Skip to main content

sipx_ua/
event_client.rs

1//! Sans-I/O subscriber state for SIP event packages (RFC 6665).
2//!
3//! This module owns no socket, task or clock. A driver applies [`Output`] values and feeds
4//! responses, NOTIFY requests and fired timer generations back through [`EventClient`]. The
5//! normative state tables and byte vectors live in `docs/specs/event-client.md`.
6
7use std::collections::HashMap;
8use std::net::SocketAddr;
9use std::sync::Arc;
10use std::time::Duration;
11
12use bytes::Bytes;
13use sipx_sip::auth::{Challenge, Credentials, respond, strongest};
14use sipx_sip::build::RequestBuilder;
15use sipx_sip::event::{Reason, State, Subscription};
16use sipx_sip::headers::{CSeq, Contact, Expires, From as FromHeader, RecordRoute, To};
17use sipx_sip::{Address, Header, HeaderName, Host, Method, Request, Response, Uri, UriTransport};
18use thiserror::Error;
19
20/// RFC 6665's Timer N: 64 times SIP's default 500 ms T1.
21pub const DEFAULT_TIMER_N: Duration = Duration::from_secs(32);
22/// Default number of logical subscriptions held by one client.
23pub const DEFAULT_CAPACITY: usize = 1_024;
24/// Default number of application deliveries retained for one subscription.
25pub const DEFAULT_DELIVERY_CAPACITY: usize = 32;
26/// Default maximum body accepted in either direction.
27pub const DEFAULT_BODY_LIMIT: usize = 65_536;
28/// Default delay for a probation termination without `retry-after`.
29pub const DEFAULT_PROBATION_BACKOFF: Duration = Duration::from_secs(60);
30
31/// A transport identity supplied by the I/O driver.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Transport {
34    /// UDP datagrams.
35    Udp,
36    /// TCP stream.
37    Tcp,
38    /// TLS stream.
39    Tls,
40    /// WebSocket stream.
41    Ws,
42    /// Secure WebSocket stream.
43    Wss,
44    /// QUIC connection.
45    Quic,
46}
47
48impl Transport {
49    fn default_port(self) -> u16 {
50        match self {
51            Self::Udp | Self::Tcp => 5060,
52            Self::Tls | Self::Quic => 5061,
53            Self::Ws => 80,
54            Self::Wss => 443,
55        }
56    }
57}
58
59/// The peer and, for a stream, exact connection generation used by an exchange.
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub struct Peer {
62    /// Remote socket address.
63    pub address: SocketAddr,
64    /// SIP transport.
65    pub transport: Transport,
66    /// Driver-defined stream generation; absent for UDP.
67    pub connection: Option<u64>,
68    /// URI authority selected before address resolution for TLS verification or WebSocket Host.
69    pub identity: Option<Arc<str>>,
70    /// WebSocket request resource; absent means `/` and is ignored outside WS/WSS.
71    pub path: Option<Arc<str>>,
72}
73
74impl Peer {
75    /// A target with no certificate identity or non-default WebSocket resource.
76    #[must_use]
77    pub fn new(address: SocketAddr, transport: Transport) -> Self {
78        Self {
79            address,
80            transport,
81            connection: None,
82            identity: None,
83            path: None,
84        }
85    }
86
87    /// Preserve the name the secure transport must verify.
88    #[must_use]
89    pub fn verifying(mut self, identity: impl AsRef<str>) -> Self {
90        self.identity = Some(Arc::from(identity.as_ref()));
91        self
92    }
93
94    /// Preserve the WebSocket request resource.
95    #[must_use]
96    pub fn at_path(mut self, path: impl AsRef<str>) -> Self {
97        let path = path.as_ref();
98        self.path = Some(if path.starts_with('/') {
99            Arc::from(path)
100        } else {
101            Arc::from(format!("/{path}"))
102        });
103        self
104    }
105}
106
107/// Default fail-closed NOTIFY origin policy.
108#[derive(Debug, Default)]
109pub struct SamePeer;
110
111/// Policy applied before a NOTIFY can select or mutate a dialog.
112pub trait NotifyTrustPolicy: Send + Sync + 'static {
113    /// Whether this request arrived through an authorized peer/connection.
114    fn accepts(&self, selected_target: &Peer, received_from: &Peer, request: &Request) -> bool;
115}
116
117impl NotifyTrustPolicy for SamePeer {
118    fn accepts(&self, selected_target: &Peer, received_from: &Peer, _request: &Request) -> bool {
119        selected_target.address == received_from.address
120            && selected_target.transport == received_from.transport
121            && selected_target.connection == received_from.connection
122    }
123}
124
125/// Package-specific parsing behind the generic event lifecycle.
126pub trait PackageConsumer: Send + 'static {
127    /// Owned application value produced by this package.
128    type Value: Send + 'static;
129
130    /// Exact Event package token.
131    fn event(&self) -> &str;
132
133    /// Optional Event `id` parameter.
134    fn event_id(&self) -> Option<&str> {
135        None
136    }
137
138    /// Values advertised in `Accept`.
139    fn accept(&self) -> &[String];
140
141    /// Neutral value delivered before the first NOTIFY, when the package defines one.
142    fn neutral(&mut self) -> Option<Self::Value>;
143
144    /// Whether an empty terminal body is valid without invoking the consumer.
145    fn empty_terminal_is_valid(&self) -> bool {
146        true
147    }
148
149    /// Parse one bounded NOTIFY body.
150    fn consume(
151        &mut self,
152        content_type: Option<&[u8]>,
153        body: &[u8],
154    ) -> Result<Self::Value, PackageRejection>;
155}
156
157/// A package-controlled final refusal.
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub struct PackageRejection {
160    /// SIP final status, normally 400 or 415.
161    pub status: u16,
162}
163
164impl PackageRejection {
165    /// Reject malformed package bytes.
166    #[must_use]
167    pub const fn malformed() -> Self {
168        Self { status: 400 }
169    }
170
171    /// Reject an unsupported media type.
172    #[must_use]
173    pub const fn unsupported_media() -> Self {
174        Self { status: 415 }
175    }
176}
177
178/// Bounded client configuration.
179#[derive(Debug, Clone)]
180pub struct Config {
181    /// Maximum logical subscription intents.
182    pub capacity: usize,
183    /// Maximum undrained deliveries per subscription.
184    pub delivery_capacity: usize,
185    /// Maximum retained NOTIFY body.
186    pub notify_body_limit: usize,
187    /// Maximum outbound SUBSCRIBE body.
188    pub subscribe_body_limit: usize,
189    /// Maximum digest retries per operation.
190    pub authentication_retries: u8,
191    /// Maximum 423 retries per operation.
192    pub interval_retries: u8,
193    /// Timer N duration.
194    pub timer_n: Duration,
195    /// Host maximum accepted interval.
196    pub maximum_expiry: Duration,
197    /// Delay used when probation omits `retry-after`.
198    pub probation_backoff: Duration,
199}
200
201impl Default for Config {
202    fn default() -> Self {
203        Self {
204            capacity: DEFAULT_CAPACITY,
205            delivery_capacity: DEFAULT_DELIVERY_CAPACITY,
206            notify_body_limit: DEFAULT_BODY_LIMIT,
207            subscribe_body_limit: DEFAULT_BODY_LIMIT,
208            authentication_retries: 2,
209            interval_retries: 1,
210            timer_n: DEFAULT_TIMER_N,
211            maximum_expiry: Duration::from_secs(u64::from(u32::MAX)),
212            probation_backoff: DEFAULT_PROBATION_BACKOFF,
213        }
214    }
215}
216
217impl Config {
218    /// Validate every peer-driven bound before allocating a client.
219    pub fn validate(&self) -> Result<(), StartError> {
220        if self.capacity == 0
221            || self.delivery_capacity == 0
222            || self.notify_body_limit == 0
223            || self.subscribe_body_limit == 0
224            || self.authentication_retries == 0
225            || self.interval_retries == 0
226            || self.timer_n.is_zero()
227            || self.maximum_expiry.is_zero()
228            || self.probation_backoff.is_zero()
229            || self.maximum_expiry.as_secs() > u64::from(u32::MAX)
230        {
231            return Err(StartError::InvalidConfiguration);
232        }
233        Ok(())
234    }
235}
236
237/// Driver-supplied fields for one initial SUBSCRIBE.
238pub struct Start<C> {
239    /// Resource URI and initial Request-URI.
240    pub resource: Uri,
241    /// Address placed in From, without a tag.
242    pub local_identity: String,
243    /// Contact value advertised by this client.
244    pub contact: String,
245    /// Selected network target.
246    pub target: Peer,
247    /// Desired positive lifetime.
248    pub expires: Duration,
249    /// Optional bounded request body.
250    pub body: Bytes,
251    /// Optional request media type when `body` is non-empty.
252    pub content_type: Option<String>,
253    /// Optional digest credentials.
254    pub credentials: Option<Credentials>,
255    /// Fresh opaque Call-ID.
256    pub call_id: String,
257    /// Fresh local From tag.
258    pub from_tag: String,
259    /// Non-zero first local `CSeq`.
260    pub initial_cseq: u32,
261    /// Package parser and state owner.
262    pub consumer: C,
263    /// NOTIFY origin authorization.
264    pub trust: Arc<dyn NotifyTrustPolicy>,
265}
266
267impl<C> std::fmt::Debug for Start<C> {
268    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        formatter
270            .debug_struct("Start")
271            .field("resource", &self.resource)
272            .field("local_identity", &self.local_identity)
273            .field("contact", &self.contact)
274            .field("target", &self.target)
275            .field("expires", &self.expires)
276            .field("body_len", &self.body.len())
277            .field("content_type", &self.content_type)
278            .field("has_credentials", &self.credentials.is_some())
279            .field("call_id", &self.call_id)
280            .field("from_tag", &self.from_tag)
281            .field("initial_cseq", &self.initial_cseq)
282            .finish_non_exhaustive()
283    }
284}
285
286/// Opaque local identity of one subscription intent.
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
288pub struct SubscriptionId(u64);
289
290impl SubscriptionId {
291    /// Stable numeric value for logs and application maps.
292    #[must_use]
293    pub fn get(self) -> u64 {
294        self.0
295    }
296}
297
298/// A fired timer name.
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
300pub enum Timer {
301    /// Initial/refresh/unsubscribe NOTIFY wait.
302    N,
303    /// Current finite subscription lifetime.
304    Expiry,
305    /// Scheduled refresh.
306    Refresh,
307    /// Package-directed retry.
308    Retry,
309}
310
311/// Public lifecycle state.
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum Lifecycle {
314    /// Waiting for the first matching NOTIFY.
315    NotifyWait,
316    /// An active subscription.
317    Active,
318    /// A pending subscription.
319    Pending,
320    /// Waiting for the terminal NOTIFY after Expires 0.
321    Unsubscribing,
322    /// Waiting for package-directed retry eligibility.
323    RetryWait,
324}
325
326/// Typed terminal outcome.
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub enum Termination {
329    /// Timer N fired before the required NOTIFY.
330    NoInitialNotify,
331    /// The finite expiry fired locally.
332    LocalExpiry,
333    /// A local request sequence could not be incremented safely.
334    LocalCSeqExhausted,
335    /// A successful response carried an invalid interval.
336    InvalidExpiry,
337    /// A successful response did not identify the selected dialog.
338    MalformedResponse,
339    /// A 423 could not be followed safely.
340    IntervalRejected,
341    /// Authentication could not be completed under the configured bound.
342    AuthenticationExhausted,
343    /// A final response rejected the logical operation.
344    Rejected(u16),
345    /// The transaction ended without a final response.
346    TransactionFailed,
347    /// A route URI selected no safe supported transport.
348    UnsupportedRouteTransport,
349    /// The notifier supplied a terminal framework reason.
350    Remote(Option<Reason>),
351    /// Unsubscribe ended without peer confirmation.
352    UnsubscribeUnconfirmed(Box<Termination>),
353    /// The global shutdown deadline released the usage.
354    Shutdown,
355}
356
357/// Observable lifecycle facts.
358#[derive(Debug, Clone, PartialEq, Eq)]
359pub enum StateChange {
360    /// The subscription entered a framework state.
361    State(Lifecycle),
362    /// A response conflicted with a usage already established by NOTIFY.
363    ConflictingSubscribeResponse,
364    /// Timer N ended a refresh attempt; the prior authoritative expiry remains unchanged.
365    RefreshUnconfirmed,
366    /// The application may start a fresh subscription with fresh dialog identity now.
367    MayRetryNow,
368    /// A terminal state released the subscription.
369    Terminated(Termination),
370}
371
372/// Framework metadata delivered beside a package value.
373#[derive(Debug, Clone, PartialEq, Eq)]
374pub struct NotificationMeta {
375    /// Parsed Subscription-State.
376    pub subscription: Subscription,
377    /// Remote NOTIFY `CSeq`.
378    pub remote_cseq: u32,
379}
380
381/// One pure action for the I/O-facing driver.
382#[derive(Debug)]
383pub enum Output<V> {
384    /// Send a complete SUBSCRIBE through a real client transaction.
385    SendSubscribe {
386        /// Logical owner.
387        id: SubscriptionId,
388        /// Complete request apart from transport-owned Via.
389        request: Box<Request>,
390        /// Selected peer.
391        target: Peer,
392    },
393    /// Answer an inbound NOTIFY server transaction.
394    RespondNotify {
395        /// Driver's server-transaction token.
396        transaction: u64,
397        /// Final status.
398        status: u16,
399        /// Retry-After for bounded delivery backpressure.
400        retry_after: Option<Duration>,
401    },
402    /// Deliver one parsed package value.
403    Deliver {
404        /// Logical owner.
405        id: SubscriptionId,
406        /// Framework metadata; absent only for the initial neutral value.
407        metadata: Option<NotificationMeta>,
408        /// Package value.
409        value: V,
410    },
411    /// Arm or replace one timer generation.
412    ArmTimer {
413        /// Logical owner.
414        id: SubscriptionId,
415        /// Timer kind.
416        timer: Timer,
417        /// New generation.
418        generation: u64,
419        /// Relative duration.
420        after: Duration,
421    },
422    /// Cancel one timer generation.
423    CancelTimer {
424        /// Logical owner.
425        id: SubscriptionId,
426        /// Timer kind.
427        timer: Timer,
428        /// Generation made stale.
429        generation: u64,
430    },
431    /// Surface a typed state fact.
432    StateChanged {
433        /// Logical owner.
434        id: SubscriptionId,
435        /// New fact.
436        change: StateChange,
437    },
438    /// Shutdown released every owned resource.
439    Stopped,
440}
441
442/// Failure before an initial request exists.
443#[derive(Debug, Error, Clone, PartialEq, Eq)]
444#[non_exhaustive]
445pub enum StartError {
446    /// A configured maximum was zero or unrepresentable.
447    #[error("invalid event-client configuration")]
448    InvalidConfiguration,
449    /// The client already owns its configured number of subscriptions.
450    #[error("event-client capacity exceeded")]
451    CapacityExceeded,
452    /// The request body exceeds its configured maximum.
453    #[error("SUBSCRIBE body exceeds configured maximum")]
454    BodyTooLarge,
455    /// An identity, interval, package or request header is invalid.
456    #[error("invalid subscription start")]
457    InvalidStart,
458    /// A SIP request could not be constructed.
459    #[error("could not build SUBSCRIBE")]
460    Build,
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464enum OperationKind {
465    Initial,
466    Refresh,
467    Unsubscribe,
468}
469
470struct Operation {
471    kind: OperationKind,
472    attempted: Duration,
473    request: Request,
474    auth_retries: u8,
475    interval_retries: u8,
476    notify_expiry: bool,
477}
478
479struct Dialog {
480    remote_tag: Vec<u8>,
481    local: String,
482    remote: String,
483    remote_target: Uri,
484    route_set: Vec<RouteHop>,
485    peer: Peer,
486    route_peer: Option<Peer>,
487    selected_peer: Option<Peer>,
488    remote_cseq: u32,
489}
490
491struct RouteHop {
492    uri: Uri,
493    wire: String,
494}
495
496#[derive(Default)]
497struct Timers {
498    n: Option<u64>,
499    expiry: Option<u64>,
500    refresh: Option<u64>,
501    retry: Option<u64>,
502    next: u64,
503}
504
505struct Entry<C> {
506    lifecycle: Lifecycle,
507    contact: String,
508    target: Peer,
509    desired: Duration,
510    body: Bytes,
511    credentials: Option<Credentials>,
512    call_id: String,
513    from_tag: String,
514    local_cseq: u32,
515    event: String,
516    event_id: Option<String>,
517    accepts: Vec<String>,
518    consumer: C,
519    trust: Arc<dyn NotifyTrustPolicy>,
520    response_tag: Option<Vec<u8>>,
521    response_expiry: Option<Duration>,
522    dialog: Option<Dialog>,
523    operation: Option<Operation>,
524    pending_unsubscribe: bool,
525    timers: Timers,
526    queued: usize,
527    retry_termination: Option<Termination>,
528}
529
530/// Reusable sans-I/O event subscriber.
531pub struct EventClient<C: PackageConsumer> {
532    config: Config,
533    entries: HashMap<SubscriptionId, Entry<C>>,
534    next_id: u64,
535    shutting_down: bool,
536}
537
538/// Outputs produced while allocating one new subscription.
539pub type Started<V> = (SubscriptionId, Vec<Output<V>>);
540
541impl<C: PackageConsumer> std::fmt::Debug for EventClient<C> {
542    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543        formatter
544            .debug_struct("EventClient")
545            .field("config", &self.config)
546            .field("active", &self.entries.len())
547            .field("next_id", &self.next_id)
548            .field("shutting_down", &self.shutting_down)
549            .finish()
550    }
551}
552
553impl<C: PackageConsumer> EventClient<C> {
554    /// Construct a bounded client.
555    pub fn new(config: Config) -> Result<Self, StartError> {
556        config.validate()?;
557        Ok(Self {
558            config,
559            entries: HashMap::new(),
560            next_id: 1,
561            shutting_down: false,
562        })
563    }
564
565    /// Number of logical intents currently owned.
566    #[must_use]
567    pub fn active(&self) -> usize {
568        self.entries.len()
569    }
570
571    /// Whether a subscription is still owned.
572    #[must_use]
573    pub fn contains(&self, id: SubscriptionId) -> bool {
574        self.entries.contains_key(&id)
575    }
576
577    /// Record the exact stream generation selected by the transport for the latest SUBSCRIBE.
578    pub fn connection_selected(&mut self, id: SubscriptionId, generation: Option<u64>) {
579        let Some(entry) = self.entries.get_mut(&id) else {
580            return;
581        };
582        if entry.dialog.is_some() {
583            let mut selected = request_peer(entry);
584            selected.connection = generation;
585            if let Some(dialog) = entry.dialog.as_mut() {
586                dialog.selected_peer = Some(selected);
587            }
588        } else {
589            entry.target.connection = generation;
590        }
591    }
592
593    /// Begin one subscription and return its ordered initial outputs.
594    pub fn start(&mut self, start: Start<C>) -> Result<Started<C::Value>, StartError> {
595        if self.shutting_down || self.entries.len() >= self.config.capacity {
596            return Err(StartError::CapacityExceeded);
597        }
598        if start.body.len() > self.config.subscribe_body_limit
599            || start.expires.is_zero()
600            || start.expires > self.config.maximum_expiry
601            || start.expires.as_secs() > u64::from(u32::MAX)
602            || start.initial_cseq == 0
603            || start.call_id.trim().is_empty()
604            || start.from_tag.trim().is_empty()
605            || start.consumer.event().trim().is_empty()
606        {
607            return Err(if start.body.len() > self.config.subscribe_body_limit {
608                StartError::BodyTooLarge
609            } else {
610                StartError::InvalidStart
611            });
612        }
613        let id = SubscriptionId(self.next_id);
614        self.next_id = self
615            .next_id
616            .checked_add(1)
617            .ok_or(StartError::CapacityExceeded)?;
618        let event = start.consumer.event().to_owned();
619        let event_id = start.consumer.event_id().map(str::to_owned);
620        let accepts = start.consumer.accept().to_vec();
621        let request = build_initial(&start, &event, event_id.as_deref())?;
622        let mut entry = Entry {
623            lifecycle: Lifecycle::NotifyWait,
624            contact: start.contact,
625            target: start.target,
626            desired: start.expires,
627            body: start.body,
628            credentials: start.credentials,
629            call_id: start.call_id,
630            from_tag: start.from_tag,
631            local_cseq: start.initial_cseq,
632            event,
633            event_id,
634            accepts,
635            consumer: start.consumer,
636            trust: start.trust,
637            response_tag: None,
638            response_expiry: None,
639            dialog: None,
640            operation: Some(Operation {
641                kind: OperationKind::Initial,
642                attempted: start.expires,
643                request: request.clone(),
644                auth_retries: 0,
645                interval_retries: 0,
646                notify_expiry: false,
647            }),
648            pending_unsubscribe: false,
649            timers: Timers::default(),
650            queued: 0,
651            retry_termination: None,
652        };
653        let mut outputs = Vec::new();
654        if let Some(value) = entry.consumer.neutral() {
655            entry.queued = 1;
656            outputs.push(Output::Deliver {
657                id,
658                metadata: None,
659                value,
660            });
661        }
662        outputs.push(Output::SendSubscribe {
663            id,
664            request: Box::new(request),
665            target: entry.target.clone(),
666        });
667        arm(&mut entry, id, Timer::N, self.config.timer_n, &mut outputs);
668        arm(&mut entry, id, Timer::Expiry, start.expires, &mut outputs);
669        outputs.push(Output::StateChanged {
670            id,
671            change: StateChange::State(Lifecycle::NotifyWait),
672        });
673        self.entries.insert(id, entry);
674        Ok((id, outputs))
675    }
676
677    /// Consume one final SUBSCRIBE response. `cnonce` is fresh driver-supplied entropy for a
678    /// possible digest retry.
679    #[allow(
680        clippy::too_many_lines,
681        reason = "the response table is kept in protocol order beside the normative state table"
682    )]
683    pub fn response(
684        &mut self,
685        id: SubscriptionId,
686        response: Option<&Response>,
687        cnonce: &str,
688    ) -> Vec<Output<C::Value>> {
689        let mut outputs = Vec::new();
690        let Some(entry) = self.entries.get_mut(&id) else {
691            return outputs;
692        };
693        let Some(mut operation) = entry.operation.take() else {
694            return outputs;
695        };
696
697        let Some(response) = response else {
698            operation_failure(
699                entry,
700                id,
701                operation.kind,
702                Termination::TransactionFailed,
703                self.config.timer_n,
704                &mut outputs,
705            );
706            finish_if_terminal(&mut self.entries, id, &outputs);
707            return outputs;
708        };
709
710        if matches!(response.status.code(), 401 | 407) {
711            let from_proxy = response.status.code() == 407;
712            let name = if from_proxy {
713                HeaderName::ProxyAuthenticate
714            } else {
715                HeaderName::WwwAuthenticate
716            };
717            let challenge = strongest(
718                response
719                    .headers
720                    .get_all(&name)
721                    .filter_map(|header| Challenge::parse(&header.value(), from_proxy))
722                    .collect(),
723            );
724            if operation.auth_retries >= self.config.authentication_retries
725                || challenge.is_none()
726                || entry.credentials.is_none()
727                || !increment_cseq(entry, id, operation.kind, &mut outputs)
728            {
729                if !outputs.iter().any(is_terminal::<C::Value>) {
730                    operation_failure(
731                        entry,
732                        id,
733                        operation.kind,
734                        Termination::AuthenticationExhausted,
735                        self.config.timer_n,
736                        &mut outputs,
737                    );
738                }
739            } else if let (Some(challenge), Some(credentials)) =
740                (challenge, entry.credentials.as_ref())
741            {
742                operation.auth_retries = operation.auth_retries.saturating_add(1);
743                let mut retry = operation.request.clone();
744                replace_cseq(&mut retry, entry.local_cseq);
745                let uri = String::from_utf8_lossy(&retry.uri.to_bytes()).into_owned();
746                let value = respond(
747                    &challenge,
748                    credentials,
749                    "SUBSCRIBE",
750                    &uri,
751                    u32::from(operation.auth_retries),
752                    cnonce,
753                );
754                retry.headers.remove_all(&challenge.response_header());
755                if let Ok(header) = Header::build(challenge.response_header(), Bytes::from(value)) {
756                    retry.headers.push(header);
757                    operation.request = retry.clone();
758                    entry.operation = Some(operation);
759                    outputs.push(Output::SendSubscribe {
760                        id,
761                        request: Box::new(retry),
762                        target: request_peer(entry),
763                    });
764                    arm(entry, id, Timer::N, self.config.timer_n, &mut outputs);
765                    return outputs;
766                }
767                operation_failure(
768                    entry,
769                    id,
770                    operation.kind,
771                    Termination::AuthenticationExhausted,
772                    self.config.timer_n,
773                    &mut outputs,
774                );
775            }
776            finish_if_terminal(&mut self.entries, id, &outputs);
777            return outputs;
778        }
779
780        if response.status.code() == 423 {
781            let minimum = strict_scalar(response, &HeaderName::MinExpires);
782            let valid = operation.kind != OperationKind::Unsubscribe
783                && operation.interval_retries < self.config.interval_retries
784                && minimum.is_some_and(|value| {
785                    value > operation.attempted
786                        && value <= self.config.maximum_expiry
787                        && u32::try_from(value.as_secs()).is_ok()
788                });
789            if valid
790                && increment_cseq(entry, id, operation.kind, &mut outputs)
791                && let Some(minimum) = minimum
792            {
793                operation.interval_retries = operation.interval_retries.saturating_add(1);
794                operation.attempted = minimum;
795                let mut retry = operation.request.clone();
796                replace_cseq(&mut retry, entry.local_cseq);
797                replace_duration_header(&mut retry, HeaderName::Expires, minimum);
798                operation.request = retry.clone();
799                entry.operation = Some(operation);
800                outputs.push(Output::SendSubscribe {
801                    id,
802                    request: Box::new(retry),
803                    target: request_peer(entry),
804                });
805                arm(entry, id, Timer::N, self.config.timer_n, &mut outputs);
806                arm(entry, id, Timer::Expiry, minimum, &mut outputs);
807                return outputs;
808            }
809            if !outputs.iter().any(is_terminal::<C::Value>) {
810                operation_failure(
811                    entry,
812                    id,
813                    operation.kind,
814                    Termination::IntervalRejected,
815                    self.config.timer_n,
816                    &mut outputs,
817                );
818            }
819            finish_if_terminal(&mut self.entries, id, &outputs);
820            return outputs;
821        }
822
823        if response.status.is_success() {
824            let granted = strict_expires(response);
825            let valid_interval = match operation.kind {
826                OperationKind::Initial | OperationKind::Refresh => {
827                    granted.is_some_and(|value| !value.is_zero() && value <= operation.attempted)
828                }
829                OperationKind::Unsubscribe => granted == Some(Duration::ZERO),
830            };
831            let tag = response_tag(response);
832            let valid_dialog = match operation.kind {
833                OperationKind::Initial => entry.dialog.is_some() || tag.is_some(),
834                OperationKind::Refresh | OperationKind::Unsubscribe => {
835                    entry.dialog.as_ref().is_some_and(|dialog| {
836                        tag.as_ref()
837                            .is_some_and(|tag| tag == dialog.remote_tag.as_slice())
838                    })
839                }
840            };
841            if !valid_interval {
842                operation_failure(
843                    entry,
844                    id,
845                    operation.kind,
846                    Termination::InvalidExpiry,
847                    self.config.timer_n,
848                    &mut outputs,
849                );
850            } else if !valid_dialog {
851                operation_failure(
852                    entry,
853                    id,
854                    operation.kind,
855                    Termination::MalformedResponse,
856                    self.config.timer_n,
857                    &mut outputs,
858                );
859            } else if let Some(granted) = granted {
860                if operation.kind == OperationKind::Unsubscribe {
861                    entry.operation = None;
862                } else {
863                    if operation.kind == OperationKind::Initial && entry.dialog.is_none() {
864                        entry.response_tag = tag;
865                    } else if operation.kind == OperationKind::Refresh {
866                        refresh_dialog_from_response(entry, response);
867                    }
868                    if !operation.notify_expiry {
869                        entry.response_expiry = Some(granted);
870                        arm(entry, id, Timer::Expiry, granted, &mut outputs);
871                        if matches!(entry.lifecycle, Lifecycle::Active | Lifecycle::Pending) {
872                            arm_refresh(entry, id, granted, &mut outputs);
873                        }
874                    }
875                    entry.operation = None;
876                    maybe_begin_pending_unsubscribe(entry, id, self.config.timer_n, &mut outputs);
877                }
878            }
879            finish_if_terminal(&mut self.entries, id, &outputs);
880            return outputs;
881        }
882
883        if operation.kind == OperationKind::Refresh && fatal_refresh_status(response.status.code())
884        {
885            terminate(
886                entry,
887                id,
888                Termination::Rejected(response.status.code()),
889                &mut outputs,
890            );
891        } else {
892            operation_failure(
893                entry,
894                id,
895                operation.kind,
896                Termination::Rejected(response.status.code()),
897                self.config.timer_n,
898                &mut outputs,
899            );
900        }
901        finish_if_terminal(&mut self.entries, id, &outputs);
902        outputs
903    }
904
905    /// Consume and answer one NOTIFY. The driver token is returned unchanged in
906    /// [`Output::RespondNotify`].
907    #[allow(
908        clippy::too_many_lines,
909        clippy::needless_pass_by_value,
910        reason = "the fail-closed NOTIFY validation order mirrors the normative decision table"
911    )]
912    pub fn notify(
913        &mut self,
914        transaction: u64,
915        request: &Request,
916        source: Peer,
917    ) -> Vec<Output<C::Value>> {
918        let mut outputs = Vec::new();
919        let Some((id, entry)) = self.entries.iter_mut().find(|(_, entry)| {
920            call_id(request).is_some_and(|call_id| call_id == entry.call_id.as_bytes())
921                && tag::<To>(request).is_some_and(|tag| tag == entry.from_tag.as_bytes())
922        }) else {
923            outputs.push(respond_notify(transaction, 481, None));
924            return outputs;
925        };
926        let id = *id;
927
928        if request.method != Method::Notify
929            || request.headers.count(&HeaderName::CallId) != 1
930            || request.headers.count(&HeaderName::From) != 1
931            || request.headers.count(&HeaderName::To) != 1
932        {
933            outputs.push(respond_notify(transaction, 400, None));
934            return outputs;
935        }
936        let Some((event, event_id)) = parse_event(request) else {
937            outputs.push(respond_notify(transaction, 489, None));
938            return outputs;
939        };
940        if !event.eq_ignore_ascii_case(&entry.event)
941            || event_id.as_deref() != entry.event_id.as_deref()
942        {
943            outputs.push(respond_notify(transaction, 489, None));
944            return outputs;
945        }
946        let Some(remote_tag) = tag::<FromHeader>(request) else {
947            outputs.push(respond_notify(transaction, 400, None));
948            return outputs;
949        };
950        if entry
951            .dialog
952            .as_ref()
953            .is_some_and(|dialog| dialog.remote_tag.as_slice() != remote_tag.as_slice())
954            || (entry.dialog.is_none()
955                && entry
956                    .response_tag
957                    .as_ref()
958                    .is_some_and(|candidate| candidate.as_slice() != remote_tag.as_slice()))
959        {
960            outputs.push(respond_notify(transaction, 481, None));
961            return outputs;
962        }
963        let selected_target = request_peer(entry);
964        if !entry.trust.accepts(&selected_target, &source, request) {
965            outputs.push(respond_notify(transaction, 403, None));
966            return outputs;
967        }
968        let cseq = if request.headers.count(&HeaderName::CSeq) == 1 {
969            if let Some(Ok(CSeq {
970                sequence,
971                method: Method::Notify,
972            })) = request.headers.typed::<CSeq>()
973            {
974                sequence
975            } else {
976                outputs.push(respond_notify(transaction, 400, None));
977                return outputs;
978            }
979        } else {
980            outputs.push(respond_notify(transaction, 400, None));
981            return outputs;
982        };
983        if entry
984            .dialog
985            .as_ref()
986            .is_some_and(|dialog| cseq <= dialog.remote_cseq)
987        {
988            outputs.push(respond_notify(transaction, 500, None));
989            return outputs;
990        }
991        let contacts: Vec<_> = request.headers.typed_all::<Contact>().collect();
992        let [Ok(contact)] = contacts.as_slice() else {
993            outputs.push(respond_notify(transaction, 400, None));
994            return outputs;
995        };
996        let state = if request.headers.count(&HeaderName::SubscriptionState) == 1 {
997            request
998                .headers
999                .value(&HeaderName::SubscriptionState)
1000                .and_then(|value| Subscription::parse(&value))
1001        } else {
1002            None
1003        };
1004        let Some(state) = state else {
1005            outputs.push(respond_notify(transaction, 400, None));
1006            return outputs;
1007        };
1008        if request.body().len() > self.config.notify_body_limit {
1009            outputs.push(respond_notify(transaction, 413, None));
1010            return outputs;
1011        }
1012        let should_consume = !request.body().is_empty()
1013            || state.state != State::Terminated
1014            || !entry.consumer.empty_terminal_is_valid();
1015        if should_consume && entry.queued >= self.config.delivery_capacity {
1016            outputs.push(respond_notify(
1017                transaction,
1018                503,
1019                Some(Duration::from_secs(1)),
1020            ));
1021            return outputs;
1022        }
1023        let value = if should_consume {
1024            let content_type = request.headers.value(&HeaderName::ContentType);
1025            match entry
1026                .consumer
1027                .consume(content_type.as_deref(), request.body())
1028            {
1029                Ok(value) => Some(value),
1030                Err(rejection) => {
1031                    outputs.push(respond_notify(transaction, rejection.status, None));
1032                    return outputs;
1033                }
1034            }
1035        } else {
1036            None
1037        };
1038
1039        let remote = request
1040            .headers
1041            .value(&HeaderName::From)
1042            .map(|value| String::from_utf8_lossy(&value).into_owned())
1043            .unwrap_or_default();
1044        let local = request
1045            .headers
1046            .value(&HeaderName::To)
1047            .map(|value| String::from_utf8_lossy(&value).into_owned())
1048            .unwrap_or_default();
1049        let routes: Result<Vec<_>, _> = request
1050            .headers
1051            .typed_all::<RecordRoute>()
1052            .map(|route| route.map(|route| route_hop(&route)))
1053            .collect();
1054        let Ok(routes) = routes else {
1055            outputs.push(respond_notify(transaction, 400, None));
1056            return outputs;
1057        };
1058        let source = Peer {
1059            identity: selected_target.identity.clone(),
1060            path: selected_target.path.clone(),
1061            ..source
1062        };
1063        let refreshed_peer = contact_peer(&contact.uri, &source);
1064        let route_peer = if entry.dialog.is_none() {
1065            if let Ok(peer) = routes
1066                .first()
1067                .map(|route| route_peer(&route.uri, &source))
1068                .transpose()
1069            {
1070                peer
1071            } else {
1072                outputs.push(respond_notify(transaction, 400, None));
1073                cancel_all(entry, id, &mut outputs);
1074                outputs.push(Output::StateChanged {
1075                    id,
1076                    change: StateChange::Terminated(Termination::UnsupportedRouteTransport),
1077                });
1078                finish_if_terminal(&mut self.entries, id, &outputs);
1079                return outputs;
1080            }
1081        } else {
1082            None
1083        };
1084        match entry.dialog.as_mut() {
1085            Some(dialog) => {
1086                dialog.remote_target = contact.uri.clone();
1087                dialog.peer = refreshed_peer;
1088                dialog.remote_cseq = cseq;
1089            }
1090            None => {
1091                entry.dialog = Some(Dialog {
1092                    remote_tag,
1093                    local,
1094                    remote,
1095                    remote_target: contact.uri.clone(),
1096                    route_set: routes,
1097                    peer: refreshed_peer,
1098                    route_peer,
1099                    selected_peer: Some(selected_target),
1100                    remote_cseq: cseq,
1101                });
1102            }
1103        }
1104        outputs.push(respond_notify(transaction, 200, None));
1105        cancel(entry, id, Timer::N, &mut outputs);
1106
1107        if let Some(operation) = entry.operation.as_mut()
1108            && state.expires.is_some()
1109        {
1110            operation.notify_expiry = true;
1111        }
1112        match state.state {
1113            State::Active | State::Pending => {
1114                entry.lifecycle = if state.state == State::Active {
1115                    Lifecycle::Active
1116                } else {
1117                    Lifecycle::Pending
1118                };
1119                if let Some(expires) = state.expires {
1120                    arm(entry, id, Timer::Expiry, expires, &mut outputs);
1121                    arm_refresh(entry, id, expires, &mut outputs);
1122                } else if let Some(expires) = entry.response_expiry {
1123                    arm_refresh(entry, id, expires, &mut outputs);
1124                }
1125                outputs.push(Output::StateChanged {
1126                    id,
1127                    change: StateChange::State(entry.lifecycle),
1128                });
1129                if let Some(value) = value {
1130                    entry.queued = entry.queued.saturating_add(1);
1131                    outputs.push(Output::Deliver {
1132                        id,
1133                        metadata: Some(NotificationMeta {
1134                            subscription: state,
1135                            remote_cseq: cseq,
1136                        }),
1137                        value,
1138                    });
1139                }
1140                maybe_begin_pending_unsubscribe(entry, id, self.config.timer_n, &mut outputs);
1141            }
1142            State::Terminated => {
1143                cancel_all(entry, id, &mut outputs);
1144                if let Some(value) = value {
1145                    outputs.push(Output::Deliver {
1146                        id,
1147                        metadata: Some(NotificationMeta {
1148                            subscription: state.clone(),
1149                            remote_cseq: cseq,
1150                        }),
1151                        value,
1152                    });
1153                }
1154                let termination = Termination::Remote(state.reason);
1155                let retry = if entry.lifecycle == Lifecycle::Unsubscribing
1156                    || entry.pending_unsubscribe
1157                    || self.shutting_down
1158                {
1159                    RetryPolicy::Never
1160                } else {
1161                    retry_policy(&state, self.config.probation_backoff)
1162                };
1163                match retry {
1164                    RetryPolicy::Never => outputs.push(Output::StateChanged {
1165                        id,
1166                        change: StateChange::Terminated(termination),
1167                    }),
1168                    RetryPolicy::Immediate => {
1169                        outputs.push(Output::StateChanged {
1170                            id,
1171                            change: StateChange::MayRetryNow,
1172                        });
1173                        outputs.push(Output::StateChanged {
1174                            id,
1175                            change: StateChange::Terminated(termination),
1176                        });
1177                    }
1178                    RetryPolicy::After(after) => {
1179                        entry.lifecycle = Lifecycle::RetryWait;
1180                        entry.retry_termination = Some(termination);
1181                        arm(entry, id, Timer::Retry, after, &mut outputs);
1182                        outputs.push(Output::StateChanged {
1183                            id,
1184                            change: StateChange::State(Lifecycle::RetryWait),
1185                        });
1186                    }
1187                }
1188            }
1189        }
1190        finish_if_terminal(&mut self.entries, id, &outputs);
1191        outputs
1192    }
1193
1194    /// Report application deliveries removed from the bounded queue.
1195    pub fn consumer_drained(&mut self, id: SubscriptionId, count: usize) {
1196        if let Some(entry) = self.entries.get_mut(&id) {
1197            entry.queued = entry.queued.saturating_sub(count);
1198        }
1199    }
1200
1201    /// Fire one exact timer generation.
1202    pub fn timer_fired(
1203        &mut self,
1204        id: SubscriptionId,
1205        timer: Timer,
1206        generation: u64,
1207    ) -> Vec<Output<C::Value>> {
1208        let mut outputs = Vec::new();
1209        let Some(entry) = self.entries.get_mut(&id) else {
1210            return outputs;
1211        };
1212        if timer_generation(&entry.timers, timer) != Some(generation) {
1213            return outputs;
1214        }
1215        set_timer(&mut entry.timers, timer, None);
1216        match timer {
1217            Timer::N => match entry.lifecycle {
1218                Lifecycle::NotifyWait => {
1219                    terminate(entry, id, Termination::NoInitialNotify, &mut outputs);
1220                }
1221                Lifecycle::Unsubscribing => terminate(
1222                    entry,
1223                    id,
1224                    Termination::UnsubscribeUnconfirmed(Box::new(Termination::NoInitialNotify)),
1225                    &mut outputs,
1226                ),
1227                Lifecycle::Active | Lifecycle::Pending => {
1228                    entry.operation = None;
1229                    outputs.push(Output::StateChanged {
1230                        id,
1231                        change: StateChange::RefreshUnconfirmed,
1232                    });
1233                    maybe_begin_pending_unsubscribe(entry, id, self.config.timer_n, &mut outputs);
1234                }
1235                Lifecycle::RetryWait => {
1236                    let reason = entry
1237                        .retry_termination
1238                        .take()
1239                        .unwrap_or(Termination::TransactionFailed);
1240                    terminate(entry, id, reason, &mut outputs);
1241                }
1242            },
1243            Timer::Expiry => terminate(entry, id, Termination::LocalExpiry, &mut outputs),
1244            Timer::Refresh => {
1245                if !self.shutting_down
1246                    && entry.operation.is_none()
1247                    && increment_cseq(entry, id, OperationKind::Refresh, &mut outputs)
1248                {
1249                    match build_in_dialog(entry, entry.desired) {
1250                        Ok(request) => {
1251                            entry.operation = Some(Operation {
1252                                kind: OperationKind::Refresh,
1253                                attempted: entry.desired,
1254                                request: request.clone(),
1255                                auth_retries: 0,
1256                                interval_retries: 0,
1257                                notify_expiry: false,
1258                            });
1259                            outputs.push(Output::SendSubscribe {
1260                                id,
1261                                request: Box::new(request),
1262                                target: request_peer(entry),
1263                            });
1264                            arm(entry, id, Timer::N, self.config.timer_n, &mut outputs);
1265                        }
1266                        Err(()) => {
1267                            terminate(entry, id, Termination::TransactionFailed, &mut outputs);
1268                        }
1269                    }
1270                }
1271            }
1272            Timer::Retry => {
1273                outputs.push(Output::StateChanged {
1274                    id,
1275                    change: StateChange::MayRetryNow,
1276                });
1277                let reason = entry
1278                    .retry_termination
1279                    .take()
1280                    .unwrap_or(Termination::TransactionFailed);
1281                terminate(entry, id, reason, &mut outputs);
1282            }
1283        }
1284        finish_if_terminal(&mut self.entries, id, &outputs);
1285        outputs
1286    }
1287
1288    /// Request an in-dialog Expires 0 operation.
1289    pub fn unsubscribe(&mut self, id: SubscriptionId) -> Vec<Output<C::Value>> {
1290        let mut outputs = Vec::new();
1291        let Some(entry) = self.entries.get_mut(&id) else {
1292            return outputs;
1293        };
1294        cancel(entry, id, Timer::Refresh, &mut outputs);
1295        cancel(entry, id, Timer::Retry, &mut outputs);
1296        if entry.lifecycle == Lifecycle::RetryWait {
1297            let reason = entry
1298                .retry_termination
1299                .take()
1300                .unwrap_or(Termination::TransactionFailed);
1301            terminate(entry, id, reason, &mut outputs);
1302        } else if entry.operation.is_some() {
1303            entry.pending_unsubscribe = true;
1304        } else if entry.dialog.is_none() {
1305            terminate(
1306                entry,
1307                id,
1308                Termination::UnsubscribeUnconfirmed(Box::new(Termination::TransactionFailed)),
1309                &mut outputs,
1310            );
1311        } else {
1312            entry.pending_unsubscribe = true;
1313            maybe_begin_pending_unsubscribe(entry, id, self.config.timer_n, &mut outputs);
1314        }
1315        finish_if_terminal(&mut self.entries, id, &outputs);
1316        outputs
1317    }
1318
1319    /// Close admission and begin one bounded unsubscribe per live usage.
1320    pub fn shutdown(&mut self) -> Vec<Output<C::Value>> {
1321        self.shutting_down = true;
1322        let ids: Vec<_> = self.entries.keys().copied().collect();
1323        let mut outputs = Vec::new();
1324        for id in ids {
1325            outputs.extend(self.unsubscribe(id));
1326        }
1327        if self.entries.is_empty() {
1328            outputs.push(Output::Stopped);
1329        }
1330        outputs
1331    }
1332
1333    /// Force the global shutdown deadline and release all state.
1334    pub fn shutdown_deadline(&mut self) -> Vec<Output<C::Value>> {
1335        let mut outputs = Vec::new();
1336        for (id, mut entry) in self.entries.drain() {
1337            cancel_all(&mut entry, id, &mut outputs);
1338            outputs.push(Output::StateChanged {
1339                id,
1340                change: StateChange::Terminated(Termination::Shutdown),
1341            });
1342        }
1343        outputs.push(Output::Stopped);
1344        outputs
1345    }
1346}
1347
1348fn build_initial<C: PackageConsumer>(
1349    start: &Start<C>,
1350    event: &str,
1351    event_id: Option<&str>,
1352) -> Result<Request, StartError> {
1353    let event = event_value(event, event_id);
1354    let mut builder = RequestBuilder::new(Method::Subscribe, start.resource.clone())
1355        .header(HeaderName::To, Bytes::from(format!("<{}>", start.resource)))
1356        .map_err(|_| StartError::Build)?
1357        .header(
1358            HeaderName::From,
1359            Bytes::from(format!("{};tag={}", start.local_identity, start.from_tag)),
1360        )
1361        .map_err(|_| StartError::Build)?
1362        .header(HeaderName::CallId, Bytes::from(start.call_id.clone()))
1363        .map_err(|_| StartError::Build)?
1364        .cseq(start.initial_cseq, &Method::Subscribe)
1365        .map_err(|_| StartError::Build)?
1366        .header(HeaderName::Contact, Bytes::from(start.contact.clone()))
1367        .map_err(|_| StartError::Build)?
1368        .header(HeaderName::Event, Bytes::from(event))
1369        .map_err(|_| StartError::Build)?
1370        .header(
1371            HeaderName::Expires,
1372            Bytes::from(start.expires.as_secs().to_string()),
1373        )
1374        .map_err(|_| StartError::Build)?
1375        .max_forwards(70);
1376    if !start.consumer.accept().is_empty() {
1377        builder = builder
1378            .header(
1379                HeaderName::Accept,
1380                Bytes::from(start.consumer.accept().join(", ")),
1381            )
1382            .map_err(|_| StartError::Build)?;
1383    }
1384    if let Some(content_type) = &start.content_type {
1385        builder = builder
1386            .header(HeaderName::ContentType, Bytes::from(content_type.clone()))
1387            .map_err(|_| StartError::Build)?;
1388    }
1389    Ok(builder.body(start.body.clone()).build())
1390}
1391
1392fn build_in_dialog<C: PackageConsumer>(entry: &Entry<C>, expires: Duration) -> Result<Request, ()> {
1393    let dialog = entry.dialog.as_ref().ok_or(())?;
1394    let event = event_value(&entry.event, entry.event_id.as_deref());
1395    let (request_uri, routes) = dialog_request_target(dialog);
1396    let mut builder = RequestBuilder::new(Method::Subscribe, request_uri)
1397        .header(HeaderName::To, Bytes::from(dialog.remote.clone()))
1398        .map_err(|_| ())?
1399        .header(HeaderName::From, Bytes::from(dialog.local.clone()))
1400        .map_err(|_| ())?
1401        .header(HeaderName::CallId, Bytes::from(entry.call_id.clone()))
1402        .map_err(|_| ())?
1403        .cseq(entry.local_cseq, &Method::Subscribe)
1404        .map_err(|_| ())?
1405        .header(HeaderName::Contact, Bytes::from(entry.contact.clone()))
1406        .map_err(|_| ())?
1407        .header(HeaderName::Event, Bytes::from(event))
1408        .map_err(|_| ())?
1409        .header(
1410            HeaderName::Expires,
1411            Bytes::from(expires.as_secs().to_string()),
1412        )
1413        .map_err(|_| ())?
1414        .max_forwards(70);
1415    if !entry.accepts.is_empty() {
1416        builder = builder
1417            .header(HeaderName::Accept, Bytes::from(entry.accepts.join(", ")))
1418            .map_err(|_| ())?;
1419    }
1420    for route in routes {
1421        builder = builder
1422            .header(HeaderName::Route, Bytes::from(route))
1423            .map_err(|_| ())?;
1424    }
1425    Ok(builder.body(entry.body.clone()).build())
1426}
1427
1428fn route_hop(address: &Address) -> RouteHop {
1429    let mut wire = format!("<{}>", address.uri);
1430    for parameter in &address.params {
1431        wire.push(';');
1432        wire.push_str(&String::from_utf8_lossy(&parameter.name));
1433        if let Some(value) = &parameter.value {
1434            wire.push('=');
1435            wire.push_str(&String::from_utf8_lossy(value));
1436        }
1437    }
1438    RouteHop {
1439        uri: address.uri.clone(),
1440        wire,
1441    }
1442}
1443
1444fn dialog_request_target(dialog: &Dialog) -> (Uri, Vec<String>) {
1445    let Some(first) = dialog.route_set.first() else {
1446        return (dialog.remote_target.clone(), Vec::new());
1447    };
1448    if first
1449        .uri
1450        .params()
1451        .is_some_and(|parameters| parameters.contains("lr"))
1452    {
1453        return (
1454            dialog.remote_target.clone(),
1455            dialog
1456                .route_set
1457                .iter()
1458                .map(|route| route.wire.clone())
1459                .collect(),
1460        );
1461    }
1462    let mut routes: Vec<_> = dialog
1463        .route_set
1464        .iter()
1465        .skip(1)
1466        .map(|route| route.wire.clone())
1467        .collect();
1468    routes.push(format!("<{}>", dialog.remote_target));
1469    (first.uri.clone(), routes)
1470}
1471
1472fn fatal_refresh_status(status: u16) -> bool {
1473    matches!(
1474        status,
1475        404 | 405 | 410 | 416 | 480 | 481 | 482 | 483 | 484 | 485 | 489 | 501 | 604
1476    )
1477}
1478
1479fn event_value(event: &str, id: Option<&str>) -> String {
1480    id.map_or_else(|| event.to_owned(), |id| format!("{event};id={id}"))
1481}
1482
1483fn replace_cseq(request: &mut Request, sequence: u32) {
1484    request.headers.remove_all(&HeaderName::CSeq);
1485    if let Ok(header) = Header::build(
1486        HeaderName::CSeq,
1487        Bytes::from(format!("{sequence} SUBSCRIBE")),
1488    ) {
1489        request.headers.push(header);
1490    }
1491}
1492
1493fn replace_duration_header(request: &mut Request, name: HeaderName, value: Duration) {
1494    request.headers.remove_all(&name);
1495    if let Ok(header) = Header::build(name, Bytes::from(value.as_secs().to_string())) {
1496        request.headers.push(header);
1497    }
1498}
1499
1500fn parse_event(request: &Request) -> Option<(String, Option<String>)> {
1501    if request.headers.count(&HeaderName::Event) != 1 {
1502        return None;
1503    }
1504    let value = request.headers.value(&HeaderName::Event)?;
1505    let text = std::str::from_utf8(&value).ok()?;
1506    let mut parts = text.split(';');
1507    let event = parts.next()?.trim();
1508    if event.is_empty() {
1509        return None;
1510    }
1511    let mut id = None;
1512    for parameter in parts {
1513        let Some((name, value)) = parameter.split_once('=') else {
1514            continue;
1515        };
1516        if name.trim().eq_ignore_ascii_case("id") {
1517            if id.is_some() {
1518                return None;
1519            }
1520            id = Some(value.trim().trim_matches('"').to_owned());
1521        }
1522    }
1523    Some((event.to_owned(), id))
1524}
1525
1526fn call_id(request: &Request) -> Option<std::borrow::Cow<'_, [u8]>> {
1527    (request.headers.count(&HeaderName::CallId) == 1)
1528        .then(|| request.headers.value(&HeaderName::CallId))
1529        .flatten()
1530}
1531
1532fn tag<H>(request: &Request) -> Option<Vec<u8>>
1533where
1534    H: sipx_sip::TypedHeader + std::ops::Deref<Target = sipx_sip::Address>,
1535{
1536    if request.headers.count(&H::NAME) != 1 {
1537        return None;
1538    }
1539    request
1540        .headers
1541        .typed::<H>()?
1542        .ok()?
1543        .tag()
1544        .map(<[u8]>::to_vec)
1545}
1546
1547fn response_tag(response: &Response) -> Option<Vec<u8>> {
1548    if response.headers.count(&HeaderName::To) != 1 {
1549        return None;
1550    }
1551    response
1552        .headers
1553        .typed::<To>()?
1554        .ok()?
1555        .tag()
1556        .map(<[u8]>::to_vec)
1557}
1558
1559fn strict_expires(response: &Response) -> Option<Duration> {
1560    if response.headers.count(&HeaderName::Expires) != 1 {
1561        return None;
1562    }
1563    response
1564        .headers
1565        .typed::<Expires>()?
1566        .ok()
1567        .map(|value| Duration::from_secs(u64::from(value.0)))
1568}
1569
1570fn strict_scalar(response: &Response, name: &HeaderName) -> Option<Duration> {
1571    if response.headers.count(name) != 1 {
1572        return None;
1573    }
1574    let value = response.headers.value(name)?;
1575    let seconds = std::str::from_utf8(&value)
1576        .ok()?
1577        .trim()
1578        .parse::<u32>()
1579        .ok()?;
1580    Some(Duration::from_secs(u64::from(seconds)))
1581}
1582
1583fn request_peer<C>(entry: &Entry<C>) -> Peer {
1584    let Some(dialog) = entry.dialog.as_ref() else {
1585        return entry.target.clone();
1586    };
1587    let mut peer = dialog.route_peer.as_ref().unwrap_or(&dialog.peer).clone();
1588    peer.connection = dialog
1589        .selected_peer
1590        .as_ref()
1591        .filter(|selected| same_target_selectors(selected, &peer))
1592        .and_then(|selected| selected.connection);
1593    peer
1594}
1595
1596fn refresh_dialog_from_response<C>(entry: &mut Entry<C>, response: &Response) {
1597    let contacts: Vec<_> = response.headers.typed_all::<Contact>().collect();
1598    let [Ok(contact)] = contacts.as_slice() else {
1599        return;
1600    };
1601    if let Some(dialog) = entry.dialog.as_mut() {
1602        dialog.remote_target = contact.uri.clone();
1603        dialog.peer = contact_peer(&contact.uri, &dialog.peer);
1604    }
1605}
1606
1607fn contact_peer(uri: &Uri, fallback: &Peer) -> Peer {
1608    let ip = match uri.host() {
1609        Some(Host::Ip(ip)) => *ip,
1610        _ => fallback.address.ip(),
1611    };
1612    let address = SocketAddr::new(
1613        ip,
1614        uri.port()
1615            .unwrap_or_else(|| fallback.transport.default_port()),
1616    );
1617    let mut peer = fallback.clone();
1618    if peer.address != address {
1619        peer.connection = None;
1620    }
1621    peer.address = address;
1622    peer
1623}
1624
1625fn route_peer(uri: &Uri, fallback: &Peer) -> Result<Peer, ()> {
1626    let selected = uri.selected_transport().map_err(|_| ())?;
1627    let transport = match selected {
1628        UriTransport::Udp => Transport::Udp,
1629        UriTransport::Tcp => Transport::Tcp,
1630        UriTransport::Tls => Transport::Tls,
1631        UriTransport::Ws => Transport::Ws,
1632        UriTransport::Wss => Transport::Wss,
1633        UriTransport::Quic => Transport::Quic,
1634    };
1635    let ip = match uri.host() {
1636        Some(Host::Ip(ip)) => *ip,
1637        Some(Host::Name(_)) => fallback.address.ip(),
1638        None => return Err(()),
1639    };
1640    let carries_authority = matches!(transport, Transport::Tls | Transport::Wss | Transport::Quic)
1641        || matches!(
1642            (transport, uri.host()),
1643            (Transport::Ws, Some(Host::Name(_)))
1644        );
1645    let identity = if carries_authority {
1646        uri.host()
1647            .map(|host| Arc::from(String::from_utf8_lossy(&host.to_bytes()).into_owned()))
1648    } else {
1649        None
1650    };
1651    let path = if matches!(transport, Transport::Ws | Transport::Wss) {
1652        fallback.path.clone()
1653    } else {
1654        None
1655    };
1656    let mut peer = Peer {
1657        address: SocketAddr::new(ip, uri.port().unwrap_or(selected.default_port())),
1658        transport,
1659        connection: None,
1660        identity,
1661        path,
1662    };
1663    if same_target_selectors(&peer, fallback) {
1664        peer.connection = fallback.connection;
1665    }
1666    Ok(peer)
1667}
1668
1669fn same_target_selectors(left: &Peer, right: &Peer) -> bool {
1670    left.address == right.address
1671        && left.transport == right.transport
1672        && left.identity == right.identity
1673        && left.path == right.path
1674}
1675
1676fn respond_notify<V>(transaction: u64, status: u16, retry_after: Option<Duration>) -> Output<V> {
1677    Output::RespondNotify {
1678        transaction,
1679        status,
1680        retry_after,
1681    }
1682}
1683
1684enum RetryPolicy {
1685    Never,
1686    Immediate,
1687    After(Duration),
1688}
1689
1690fn retry_policy(state: &Subscription, probation_backoff: Duration) -> RetryPolicy {
1691    match state.reason {
1692        Some(Reason::Deactivated) => RetryPolicy::After(Duration::ZERO),
1693        Some(Reason::Timeout) => RetryPolicy::Immediate,
1694        Some(Reason::Probation) => {
1695            RetryPolicy::After(state.retry_after.unwrap_or(probation_backoff))
1696        }
1697        Some(Reason::GiveUp) | None => state
1698            .retry_after
1699            .map_or(RetryPolicy::Immediate, RetryPolicy::After),
1700        Some(Reason::Rejected | Reason::NoResource | Reason::Invariant | Reason::BadFilter) => {
1701            RetryPolicy::Never
1702        }
1703    }
1704}
1705
1706fn timer_slot(timers: &mut Timers, timer: Timer) -> &mut Option<u64> {
1707    match timer {
1708        Timer::N => &mut timers.n,
1709        Timer::Expiry => &mut timers.expiry,
1710        Timer::Refresh => &mut timers.refresh,
1711        Timer::Retry => &mut timers.retry,
1712    }
1713}
1714
1715fn timer_generation(timers: &Timers, timer: Timer) -> Option<u64> {
1716    match timer {
1717        Timer::N => timers.n,
1718        Timer::Expiry => timers.expiry,
1719        Timer::Refresh => timers.refresh,
1720        Timer::Retry => timers.retry,
1721    }
1722}
1723
1724fn set_timer(timers: &mut Timers, timer: Timer, generation: Option<u64>) {
1725    *timer_slot(timers, timer) = generation;
1726}
1727
1728fn arm<C, V>(
1729    entry: &mut Entry<C>,
1730    id: SubscriptionId,
1731    timer: Timer,
1732    after: Duration,
1733    outputs: &mut Vec<Output<V>>,
1734) {
1735    entry.timers.next = entry.timers.next.saturating_add(1);
1736    let generation = entry.timers.next;
1737    set_timer(&mut entry.timers, timer, Some(generation));
1738    outputs.push(Output::ArmTimer {
1739        id,
1740        timer,
1741        generation,
1742        after,
1743    });
1744}
1745
1746fn cancel<C, V>(
1747    entry: &mut Entry<C>,
1748    id: SubscriptionId,
1749    timer: Timer,
1750    outputs: &mut Vec<Output<V>>,
1751) {
1752    if let Some(generation) = timer_generation(&entry.timers, timer) {
1753        set_timer(&mut entry.timers, timer, None);
1754        entry.timers.next = entry.timers.next.saturating_add(1);
1755        outputs.push(Output::CancelTimer {
1756            id,
1757            timer,
1758            generation,
1759        });
1760    }
1761}
1762
1763fn cancel_all<C, V>(entry: &mut Entry<C>, id: SubscriptionId, outputs: &mut Vec<Output<V>>) {
1764    for timer in [Timer::N, Timer::Expiry, Timer::Refresh, Timer::Retry] {
1765        cancel(entry, id, timer, outputs);
1766    }
1767}
1768
1769fn arm_refresh<C, V>(
1770    entry: &mut Entry<C>,
1771    id: SubscriptionId,
1772    expires: Duration,
1773    outputs: &mut Vec<Output<V>>,
1774) {
1775    let seconds = expires.as_secs();
1776    let refresh = if seconds <= 1 {
1777        Duration::ZERO
1778    } else {
1779        Duration::from_secs((seconds.saturating_mul(4) / 5).clamp(1, seconds - 1))
1780    };
1781    arm(entry, id, Timer::Refresh, refresh, outputs);
1782}
1783
1784fn increment_cseq<C, V>(
1785    entry: &mut Entry<C>,
1786    id: SubscriptionId,
1787    kind: OperationKind,
1788    outputs: &mut Vec<Output<V>>,
1789) -> bool {
1790    let Some(next) = entry.local_cseq.checked_add(1) else {
1791        let reason = if kind == OperationKind::Unsubscribe {
1792            Termination::UnsubscribeUnconfirmed(Box::new(Termination::LocalCSeqExhausted))
1793        } else {
1794            Termination::LocalCSeqExhausted
1795        };
1796        terminate(entry, id, reason, outputs);
1797        return false;
1798    };
1799    entry.local_cseq = next;
1800    true
1801}
1802
1803fn operation_failure<C: PackageConsumer, V>(
1804    entry: &mut Entry<C>,
1805    id: SubscriptionId,
1806    kind: OperationKind,
1807    reason: Termination,
1808    timer_n: Duration,
1809    outputs: &mut Vec<Output<V>>,
1810) {
1811    match entry.lifecycle {
1812        Lifecycle::NotifyWait | Lifecycle::RetryWait => terminate(entry, id, reason, outputs),
1813        Lifecycle::Active | Lifecycle::Pending => {
1814            cancel(entry, id, Timer::N, outputs);
1815            outputs.push(Output::StateChanged {
1816                id,
1817                change: if kind == OperationKind::Refresh {
1818                    StateChange::RefreshUnconfirmed
1819                } else {
1820                    StateChange::ConflictingSubscribeResponse
1821                },
1822            });
1823            entry.operation = None;
1824            maybe_begin_pending_unsubscribe(entry, id, timer_n, outputs);
1825        }
1826        Lifecycle::Unsubscribing => terminate(
1827            entry,
1828            id,
1829            Termination::UnsubscribeUnconfirmed(Box::new(reason)),
1830            outputs,
1831        ),
1832    }
1833}
1834
1835fn maybe_begin_pending_unsubscribe<C: PackageConsumer, V>(
1836    entry: &mut Entry<C>,
1837    id: SubscriptionId,
1838    timer_n: Duration,
1839    outputs: &mut Vec<Output<V>>,
1840) {
1841    if !entry.pending_unsubscribe || entry.operation.is_some() || entry.dialog.is_none() {
1842        return;
1843    }
1844    entry.pending_unsubscribe = false;
1845    if !increment_cseq(entry, id, OperationKind::Unsubscribe, outputs) {
1846        return;
1847    }
1848    match build_in_dialog(entry, Duration::ZERO) {
1849        Ok(request) => {
1850            entry.lifecycle = Lifecycle::Unsubscribing;
1851            entry.operation = Some(Operation {
1852                kind: OperationKind::Unsubscribe,
1853                attempted: Duration::ZERO,
1854                request: request.clone(),
1855                auth_retries: 0,
1856                interval_retries: 0,
1857                notify_expiry: false,
1858            });
1859            outputs.push(Output::SendSubscribe {
1860                id,
1861                request: Box::new(request),
1862                target: request_peer(entry),
1863            });
1864            arm(entry, id, Timer::N, timer_n, outputs);
1865            outputs.push(Output::StateChanged {
1866                id,
1867                change: StateChange::State(Lifecycle::Unsubscribing),
1868            });
1869        }
1870        Err(()) => terminate(
1871            entry,
1872            id,
1873            Termination::UnsubscribeUnconfirmed(Box::new(Termination::TransactionFailed)),
1874            outputs,
1875        ),
1876    }
1877}
1878
1879fn terminate<C, V>(
1880    entry: &mut Entry<C>,
1881    id: SubscriptionId,
1882    reason: Termination,
1883    outputs: &mut Vec<Output<V>>,
1884) {
1885    cancel_all(entry, id, outputs);
1886    entry.operation = None;
1887    entry.pending_unsubscribe = false;
1888    outputs.push(Output::StateChanged {
1889        id,
1890        change: StateChange::Terminated(reason),
1891    });
1892}
1893
1894fn is_terminal<V>(output: &Output<V>) -> bool {
1895    matches!(
1896        output,
1897        Output::StateChanged {
1898            change: StateChange::Terminated(_),
1899            ..
1900        }
1901    )
1902}
1903
1904fn finish_if_terminal<C, V>(
1905    entries: &mut HashMap<SubscriptionId, Entry<C>>,
1906    id: SubscriptionId,
1907    outputs: &[Output<V>],
1908) {
1909    if outputs.iter().any(is_terminal) {
1910        entries.remove(&id);
1911    }
1912}