Skip to main content

sipx_ua/
registrar.rs

1//! Registration (RFC 3261 §10): telling a registrar where to reach you, and keeping it told.
2//!
3//! A registration is not a request, it is a lease. The interesting parts are all about the
4//! lease rather than the message:
5//!
6//! - The registrar decides the expiry, not the client. Asking for 3600 and being granted 60 is
7//!   normal, and a client that refreshes on its own number instead of the granted one
8//!   de-registers itself every time.
9//! - The refresh has to happen *before* the lease ends, with enough margin to retry. sipx uses
10//!   90% of the granted interval, floored so a very short lease still leaves room.
11//! - `Call-ID` stays the same across refreshes and `CSeq` increases. A new `Call-ID` makes it a
12//!   new registration rather than a refresh, which is how a client ends up with duplicate
13//!   contacts at the registrar.
14//! - A 401 or 407 is expected on the first attempt, not an error.
15
16use std::net::{IpAddr, SocketAddr};
17use std::time::Duration;
18
19use bytes::Bytes;
20use sipx_sip::build::RequestBuilder;
21use sipx_sip::headers::{ContactValue, Via, first_hop_end};
22use sipx_sip::{Address, HeaderName, Method, Request, Response, Uri};
23
24use crate::auth::{Challenge, Credentials, new_cnonce, respond, strongest};
25use crate::gruu::{self, Gruus};
26use crate::outbound::{InstanceId, RegId};
27use crate::push::{self, Support};
28
29/// How long a registration lease has left, and when to refresh it.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct Lease {
32    /// What the registrar granted.
33    pub granted: Duration,
34    /// When to refresh.
35    pub refresh_after: Duration,
36}
37
38impl Lease {
39    /// The refresh point for a granted interval.
40    ///
41    /// 90% of the lease, so a failed refresh still has time for the transaction to time out
42    /// and be retried before the registration actually lapses. A refresh at 100% is a
43    /// registration that lapses whenever a single packet is lost.
44    #[must_use]
45    pub fn from_granted(granted: Duration) -> Self {
46        let seconds = granted.as_secs();
47        let refresh = if seconds <= 20 {
48            // Very short leases are used by test harnesses and some SBCs. Ten seconds of
49            // margin does not fit in a 15-second lease, so fall back to half.
50            seconds / 2
51        } else {
52            seconds * 9 / 10
53        };
54        Self {
55            granted,
56            refresh_after: Duration::from_secs(refresh.max(1)),
57        }
58    }
59}
60
61/// What the registrar's top response `Via` says it observed as this registration's source.
62///
63/// This is an observation only. It is never copied into `Contact`, routing, GRUU, Outbound, push,
64/// SDP or media policy. [`Self::Absent`] and [`Self::Invalid`] do not make an otherwise successful
65/// registration fail, while [`Self::NotRegistered`] says there has not been a successful response
66/// to inspect yet.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
68#[non_exhaustive]
69pub enum RegistrationObservation {
70    /// No REGISTER has completed successfully, so there is no response `Via` to report.
71    #[default]
72    NotRegistered,
73    /// The top `Via` was valid and carried neither `received` nor `rport`.
74    Absent,
75    /// The registrar reported one unambiguous IP address and port.
76    Observed(SocketAddr),
77    /// Observation fields were present but invalid, or the required top `Via` was unusable.
78    Invalid(RegistrationObservationError),
79}
80
81impl RegistrationObservation {
82    /// The observed address, only when the registrar stated one unambiguously.
83    #[must_use]
84    pub const fn address(self) -> Option<SocketAddr> {
85        match self {
86            Self::Observed(address) => Some(address),
87            Self::NotRegistered | Self::Absent | Self::Invalid(_) => None,
88        }
89    }
90
91    /// Interpret the top `Via` of one successful REGISTER response.
92    #[must_use]
93    pub fn from_response(response: &Response) -> Self {
94        let Some(header) = response.headers.get(&HeaderName::Via) else {
95            return Self::Invalid(RegistrationObservationError::MissingVia);
96        };
97        let value = header.value();
98        let Some(top_hop) = value.get(..first_hop_end(&value)) else {
99            return Self::Invalid(RegistrationObservationError::MalformedVia);
100        };
101        let Ok(via) = Via::parse_one(top_hop) else {
102            return Self::Invalid(RegistrationObservationError::MalformedVia);
103        };
104
105        let received: Vec<_> = via
106            .params
107            .iter()
108            .filter(|parameter| parameter.is("received"))
109            .collect();
110        if received.len() > 1 {
111            return Self::Invalid(RegistrationObservationError::ContradictoryReceived);
112        }
113        let rport: Vec<_> = via
114            .params
115            .iter()
116            .filter(|parameter| parameter.is("rport"))
117            .collect();
118        if rport.len() > 1 {
119            return Self::Invalid(RegistrationObservationError::ContradictoryRport);
120        }
121
122        let received = received.first().map(|parameter| parameter.value.as_deref());
123        let rport = rport.first().map(|parameter| parameter.value.as_deref());
124        match (received, rport) {
125            (None, None) => return Self::Absent,
126            (None, Some(_)) => {
127                return Self::Invalid(RegistrationObservationError::MissingReceived);
128            }
129            (Some(_), None | Some(None)) => {
130                return Self::Invalid(RegistrationObservationError::MissingRport);
131            }
132            (Some(_), Some(Some(_))) => {}
133        }
134
135        let Some(received) = received.flatten().and_then(parse_observed_ip) else {
136            return Self::Invalid(RegistrationObservationError::NonIpReceived);
137        };
138        let Some(rport) = rport
139            .flatten()
140            .filter(|value| value.iter().all(u8::is_ascii_digit))
141            .and_then(|value| std::str::from_utf8(value).ok())
142            .and_then(|value| value.parse::<u16>().ok())
143            .filter(|port| *port != 0)
144        else {
145            return Self::Invalid(RegistrationObservationError::InvalidRport);
146        };
147        Self::Observed(SocketAddr::new(received, rport))
148    }
149}
150
151/// Why a successful REGISTER response did not contain one usable path observation.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153#[non_exhaustive]
154pub enum RegistrationObservationError {
155    /// The successful response carried no `Via` header.
156    MissingVia,
157    /// The first `Via` header could not be parsed into a top hop.
158    MalformedVia,
159    /// The top hop asserted `received` more than once.
160    ContradictoryReceived,
161    /// The top hop asserted `rport` more than once.
162    ContradictoryRport,
163    /// `rport` was present but `received` was absent.
164    MissingReceived,
165    /// `received` was present but `rport` was absent or valueless.
166    MissingRport,
167    /// `received` was valueless or was not an IPv4 or IPv6 literal.
168    NonIpReceived,
169    /// `rport` was not a decimal port in `1..=65535`.
170    InvalidRport,
171}
172
173fn parse_observed_ip(raw: &[u8]) -> Option<IpAddr> {
174    let text = std::str::from_utf8(raw).ok()?;
175    let unbracketed = match text
176        .strip_prefix('[')
177        .and_then(|without_open| without_open.strip_suffix(']'))
178    {
179        Some(address) => address,
180        None if text.starts_with('[') || text.ends_with(']') => return None,
181        None => text,
182    };
183    unbracketed.parse().ok()
184}
185
186/// A successful registration: the lease, and the two route vectors that came back with it.
187///
188/// A struct rather than three positional fields on the enum variant, because `PathSet` and
189/// `ServiceRoute` are the same shape and opposite directions — `Path` routes requests *toward*
190/// this UA and is not ours to follow, `Service-Route` routes the requests we *send*. Positionally
191/// interchangeable arguments of identical type are how they would eventually get swapped.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct Registered {
194    /// What the registrar granted, and when to refresh it.
195    pub lease: Lease,
196    /// The source address the registrar reported in the top response `Via`.
197    ///
198    /// Informational only: it never rewrites a `Contact`, route or media address. Invalid or absent
199    /// observations do not change the lease or any other registration result.
200    pub observation: RegistrationObservation,
201    /// The proxies the registrar recorded on the path back to this contact (RFC 3327).
202    pub path: PathSet,
203    /// The proxies this UA's own outbound requests must traverse (RFC 3608).
204    pub service_route: ServiceRoute,
205    /// Whether the registrar reports having performed an *outbound* registration (RFC 5626 §6).
206    ///
207    /// §6 requires a registrar that did to say so in `Require`. Believing it happened without
208    /// being told means keeping a flow alive that nothing is routing down, and treating a binding
209    /// that is only as durable as its NAT mapping as though it were durable.
210    pub flow_accepted: bool,
211    /// The `Flow-Timer` the registrar named, if any (RFC 5626 §4.4).
212    ///
213    /// How long it will hold the flow open without traffic. When present it replaces the UA's own
214    /// choice of keep-alive interval outright.
215    pub flow_timer: Option<Duration>,
216    /// The GRUUs the registrar issued for this instance's binding (RFC 5627 §4.2).
217    ///
218    /// Empty when GRUU was not asked for, and empty when it was and the registrar issued
219    /// nothing — §4.2 makes both ordinary. They travel with the binding rather than beside it
220    /// because they expire with it: a GRUU outliving the registration that produced it is an
221    /// address a UA would keep publishing after it had stopped resolving.
222    pub gruus: Gruus,
223    /// What the registrar said about push notifications (RFC 8599 §8.2).
224    ///
225    /// Empty when push was not asked for, and empty when it was and the registrar implements
226    /// nothing of RFC 8599 — which is not a refusal. The case worth reading it for is the third
227    /// one: a registrar that answered 200 while naming a *different* push service has recorded a
228    /// binding nothing will ever wake, and this is the only place that says so.
229    pub push: Support,
230}
231
232/// What a registration attempt produced.
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum Outcome {
235    /// Registered, with the lease the registrar granted and the route vectors it returned.
236    Registered(Box<Registered>),
237    /// The registrar wants credentials. Answer with [`authorize`] and send again.
238    Challenged(Box<Challenge>),
239    /// 555: the registrar does not support the push notification service this `Contact` named
240    /// (RFC 8599 §8.1).
241    ///
242    /// Its own variant rather than a [`Outcome::Rejected`] with a number in it, because it is the
243    /// one refusal that is not about *this attempt*. Credentials can be corrected and a lease can
244    /// be re-asked for; a push service the registrar cannot use will not become usable on the next
245    /// try, and a client that retries into it is unreachable for as long as it keeps trying. The
246    /// answer is to register without push, or with a service the registrar names — which is what
247    /// [`Registered::push`] reports.
248    PushNotSupported {
249        /// The reason phrase, which §8.1 registers as [`push::NOT_SUPPORTED_REASON`].
250        reason: String,
251    },
252    /// The registrar refused.
253    Rejected {
254        /// The status code.
255        status: u16,
256        /// Its reason phrase.
257        reason: String,
258    },
259}
260
261/// What to register.
262#[derive(Debug, Clone)]
263pub struct Registration {
264    /// The registrar's URI, which is the Request-URI of the REGISTER.
265    pub registrar: Uri,
266    /// The address of record being registered.
267    pub aor: String,
268    /// Where to reach this user agent.
269    pub contact: String,
270    /// How long a lease to ask for.
271    pub expires: Duration,
272    /// The `Call-ID`, constant across refreshes.
273    pub call_id: String,
274    /// The `CSeq`, increasing across refreshes.
275    pub cseq: u32,
276    /// The device identity this registration presents (RFC 5626 §4.1, RFC 5627 §4.1).
277    ///
278    /// **One field, and that is the point.** Outbound and GRUU both identify the instance with
279    /// the same `+sip.instance` media feature tag, and they must present the same value: a
280    /// registrar that correlates them sees one device asking to be two. Two fields would
281    /// eventually hold two values, and the resulting fault appears at the registrar rather than
282    /// here — which is the worst place to discover it.
283    ///
284    /// When set, the `Contact` carries `+sip.instance`.
285    pub instance: Option<InstanceId>,
286    /// Which Outbound flow this registration is (RFC 5626 §4.2), when Outbound is in use.
287    ///
288    /// Together with [`Registration::instance`] it makes the `Contact` an Outbound one and has
289    /// the REGISTER offer the `outbound` option tag. A `reg-id` without an instance is not an
290    /// Outbound registration — §4.2 needs both — and is ignored rather than half-offered.
291    pub reg_id: Option<RegId>,
292    /// Which GRUU this UA will use once the registrar issues them (RFC 5627 §4.4).
293    ///
294    /// `Some` asks for one: §4.1 has the REGISTER offer the `gruu` option tag alongside the
295    /// instance ID. `None` does not ask, and no registrar will volunteer.
296    pub gruu: Option<gruu::Kind>,
297    /// How a push notification service can wake this device (RFC 8599 §4.1.2).
298    ///
299    /// Beside the instance identity rather than in a story of its own, because it is the same
300    /// claim: this is the device, and *this* is how to reach it when there is no flow. When set,
301    /// the `Contact` **URI** carries `pn-provider`, `pn-param` and `pn-prid` — inside the angle
302    /// brackets, which is where a registrar's URI parser looks.
303    ///
304    /// `None` registers without push, which is every client that holds a connection of its own.
305    pub push: Option<sipx_sip::push::Device>,
306    /// Validated application-owned fields repeated on retries and refreshes.
307    pub headers: Vec<sipx_sip::Header>,
308}
309
310/// The proxies a registrar recorded as being on the path back to this contact (RFC 3327).
311///
312/// Held and reported, not routed on. RFC 3327 §5.1 is explicit that "the general operation of
313/// the UA is to ignore the Path header field in the response" — the path vector exists so that
314/// requests arriving *at* the registrar can be routed toward a UA behind a NAT, and it is the
315/// registrar that walks it, not the UA. A UA that turned it into a pre-loaded route set would
316/// be sending its own requests through proxies that never asked to carry them; the header for
317/// that job is `Service-Route` (RFC 3608), which is a different list with different semantics.
318///
319/// What §5.1 does say it is for is inspection: "such inspection might allow the UA to detect
320/// intermediate proxies that have inappropriately added themselves". That is only possible if
321/// the value survives, which is why it is kept rather than parsed and dropped.
322#[derive(Debug, Clone, Default)]
323pub struct PathSet(pub Vec<Address>);
324
325impl PathSet {
326    /// The proxies, outermost first — the order they appeared in, which is the order a request
327    /// travelling toward the UA would traverse them.
328    #[must_use]
329    pub fn hops(&self) -> &[Address] {
330        &self.0
331    }
332
333    /// Whether the registrar recorded no path at all.
334    #[must_use]
335    pub fn is_empty(&self) -> bool {
336        self.0.is_empty()
337    }
338
339    /// Each hop rendered back to the form it arrived in, for logging and for comparison.
340    #[must_use]
341    pub fn rendered(&self) -> Vec<String> {
342        render_hops(&self.0)
343    }
344
345    /// Whether a proxy this side did not expect is on the path.
346    ///
347    /// RFC 3327 §5.1 gives inspection as the UA's reason to care: "such inspection might allow
348    /// the UA to detect intermediate proxies that have inappropriately added themselves". That
349    /// judgement needs a policy the UA holds, so this asks the question and leaves the answer
350    /// to the caller rather than inventing a trust rule here.
351    #[must_use]
352    pub fn hops_outside(&self, expected: &[&str]) -> Vec<String> {
353        self.rendered()
354            .into_iter()
355            .filter(|hop| !expected.iter().any(|allowed| hop.contains(allowed)))
356            .collect()
357    }
358
359    /// Read the path vector out of a REGISTER response.
360    ///
361    /// Parsed rather than kept as text, and kept as [`Address`] rather than as a URI, because
362    /// the parameters are load-bearing: RFC 5626 §5.3 hangs the `ob` marker off a `Path` value,
363    /// and `T-15` needs to read it. A path vector flattened to a list of URIs would be
364    /// syntactically fine and quietly useless for Outbound.
365    #[must_use]
366    pub fn from_response(response: &Response) -> Self {
367        Self(
368            response
369                .headers
370                .typed_all::<sipx_sip::headers::address::Path>()
371                .filter_map(std::result::Result::ok)
372                .map(|path| path.0)
373                .collect(),
374        )
375    }
376}
377
378impl PartialEq for PathSet {
379    fn eq(&self, other: &Self) -> bool {
380        self.rendered() == other.rendered()
381    }
382}
383
384impl Eq for PathSet {}
385
386/// The proxies a registrar says this UA's own requests must traverse (RFC 3608).
387///
388/// The opposite direction from [`PathSet`], and — unlike `Path` — this one *is* the UA's to act
389/// on. RFC 3608 §6: the route "applies only to requests originating in the user agent", and §6.1
390/// has the UA "use the content of the Service-Route header field as a preloaded Route header
391/// field in outgoing initial requests". Without it, a request sipx sends goes straight at the
392/// destination and arrives at a proxy holding no state for the registration it belongs to.
393///
394/// Order is normative: §6.1 requires a UA that exercises the route to "preserve the order" the
395/// values arrived in.
396#[derive(Debug, Clone, Default)]
397pub struct ServiceRoute(pub Vec<Address>);
398
399impl ServiceRoute {
400    /// The proxies, in the order the registrar listed them — which is the order to traverse.
401    #[must_use]
402    pub fn hops(&self) -> &[Address] {
403        &self.0
404    }
405
406    /// Whether the registrar dictated no route at all.
407    #[must_use]
408    pub fn is_empty(&self) -> bool {
409        self.0.is_empty()
410    }
411
412    /// Each hop rendered as a `Route` header value, in order.
413    ///
414    /// This is the form to preload: `Route: <sip:proxy.example;lr>`, one per hop.
415    #[must_use]
416    pub fn rendered(&self) -> Vec<String> {
417        render_hops(&self.0)
418    }
419
420    /// Read the service route out of a REGISTER response.
421    ///
422    /// **Absent means empty, and empty means clear.** RFC 3608 §6.1 says the stored value "is
423    /// updated according to the Service-Route header field of the latest 200 class response",
424    /// and that "if there is no Service-Route header field in the response, the UA clears any
425    /// service route for that address-of-record previously stored". Both rules are the same rule
426    /// — replace unconditionally — which is why this returns an empty set rather than an
427    /// `Option` a caller could mistake for "leave what you had".
428    #[must_use]
429    pub fn from_response(response: &Response) -> Self {
430        Self(
431            response
432                .headers
433                .typed_all::<sipx_sip::headers::address::ServiceRoute>()
434                .filter_map(std::result::Result::ok)
435                .map(|route| route.0)
436                .collect(),
437        )
438    }
439
440    /// The hops the registrar sent without the `;lr` parameter RFC 3608 §5 requires.
441    ///
442    /// §5: values "MUST include the loose-routing indicator parameter `;lr`". A hop without it
443    /// asks for RFC 2543 strict routing, where each proxy rewrites the Request-URI — a mechanism
444    /// sipx does not implement and would be wrong to pretend to. Reported rather than rejected:
445    /// the offending party is the registrar, the request will still reach the proxy named, and a
446    /// UA that discarded the whole route set over a missing parameter would be unroutable for a
447    /// reason its operator could not see.
448    ///
449    /// `lr` is a *URI* parameter — inside the angle brackets — not a header parameter after them.
450    /// Looking for it in the wrong list finds nothing and reports every hop, which is how this
451    /// method was wrong the first time it was written.
452    #[must_use]
453    pub fn hops_without_loose_routing(&self) -> Vec<String> {
454        self.0
455            .iter()
456            .filter(|hop| {
457                // `contains`, not `value`: `;lr` is a valueless flag, and `value` returns `None`
458                // for a present-but-valueless parameter as well as an absent one.
459                hop.uri.params().is_none_or(|params| !params.contains("lr"))
460            })
461            .map(|hop| format!("<{}>", String::from_utf8_lossy(&hop.uri.to_bytes())))
462            .collect()
463    }
464}
465
466impl PartialEq for ServiceRoute {
467    fn eq(&self, other: &Self) -> bool {
468        self.rendered() == other.rendered()
469    }
470}
471
472impl Eq for ServiceRoute {}
473
474/// Render address-list hops back to the header values they arrived as, in order.
475///
476/// Shared by the two route vectors deliberately: they render identically, and only their
477/// *meaning* differs. Keeping one renderer means a fix to the parameter handling cannot apply to
478/// one direction and not the other.
479fn render_hops(hops: &[Address]) -> Vec<String> {
480    hops.iter()
481        .map(|hop| {
482            let mut text = format!("<{}>", String::from_utf8_lossy(&hop.uri.to_bytes()));
483            for param in &hop.params {
484                text.push(';');
485                text.push_str(&String::from_utf8_lossy(&param.name));
486                if let Some(value) = &param.value {
487                    text.push('=');
488                    text.push_str(&String::from_utf8_lossy(value));
489                }
490            }
491            text
492        })
493        .collect()
494}
495
496impl Registration {
497    /// Build the REGISTER request.
498    ///
499    /// Note the two URIs that are easy to confuse: the Request-URI names the *registrar*, the
500    /// `To` names the *user*. A REGISTER addressed to the user reaches nothing.
501    pub fn request(&self) -> Result<Request, sipx_sip::error::BuildError> {
502        let mut builder = RequestBuilder::new(Method::Register, self.registrar.clone())
503            .header(HeaderName::To, Bytes::from(self.aor.clone()))?
504            .header(
505                HeaderName::From,
506                Bytes::from(format!("{};tag={}", self.aor, new_cnonce())),
507            )?
508            .header(HeaderName::CallId, Bytes::from(self.call_id.clone()))?
509            .cseq(self.cseq, &Method::Register)?
510            .header(HeaderName::Contact, Bytes::from(self.contact()))?
511            // RFC 3327 §5.1: a UA "SHOULD include the option tag 'path' ... in all
512            // Supported header fields". Without it §5.2 tells intermediate proxies not to
513            // add themselves, so a UA that stays quiet here is unreachable from behind the
514            // very proxies the mechanism exists to traverse.
515            .header(HeaderName::Supported, Bytes::from(self.supported()))?
516            .header(
517                HeaderName::Expires,
518                Bytes::from(self.expires.as_secs().to_string()),
519            )?
520            .max_forwards(70);
521        for header in &self.headers {
522            builder = builder.header(
523                header.name().clone(),
524                Bytes::copy_from_slice(header.raw_value()),
525            )?;
526        }
527        Ok(builder.build())
528    }
529
530    /// The `Contact` to register: the configured one, plus whatever the instance identity adds.
531    ///
532    /// `+sip.instance` appears exactly once, however many mechanisms want it — RFC 5626 §4.1 and
533    /// RFC 5627 §4.1 name the same tag, and it is emitted from the one field that holds it.
534    ///
535    /// The push parameters go on first and go *inside*: RFC 8599 §8.7 registers them as URI
536    /// parameters, so they belong in the URI's own grammar, while `+sip.instance` and `reg-id` are
537    /// `contact-param`s and belong after the angle brackets. Two lists, two meanings, and putting
538    /// either in the other's place produces a `Contact` that parses and says the wrong thing.
539    #[must_use]
540    pub fn contact(&self) -> String {
541        let base = match &self.push {
542            Some(device) => push::in_contact(&self.contact, device),
543            None => self.contact.clone(),
544        };
545        match (&self.instance, self.reg_id) {
546            // An Outbound flow: `reg-id` and the instance, in RFC 5626 §4.2's order.
547            (Some(instance), Some(reg_id)) => crate::outbound::contact(&base, instance, reg_id),
548            // An instance without a flow — a GRUU registration, or a UA that wants a registrar to
549            // recognise it across reboots without asking for Outbound.
550            (Some(instance), None) => format!("{};{}", base, instance.contact_param()),
551            (None, _) => base,
552        }
553    }
554
555    /// The option tags this REGISTER offers.
556    ///
557    /// `path` always (RFC 3327 §5.1); `outbound` when this is a flow, which RFC 5626 §4.2 makes a
558    /// MUST because a registrar has no other way to know the request wants flow semantics; and
559    /// `gruu` when one is wanted, which RFC 5627 §4.1 likewise makes a MUST.
560    ///
561    /// Both extras are conditioned on there being an instance to name. Offering either tag while
562    /// sending no `+sip.instance` asks a registrar for a mechanism defined in terms of an
563    /// identity the request never supplies, and what comes back is a rejection that reads like
564    /// something else entirely.
565    #[must_use]
566    fn supported(&self) -> String {
567        let mut tags = vec!["path"];
568        if self.instance.is_some() {
569            if self.reg_id.is_some() {
570                tags.push(crate::outbound::OPTION_TAG);
571            }
572            if self.gruu.is_some() {
573                tags.push(gruu::OPTION_TAG);
574            }
575        }
576        tags.join(", ")
577    }
578
579    /// Advance the sequence number for the next attempt.
580    ///
581    /// The `Call-ID` deliberately does not change: a new one makes this a new registration
582    /// rather than a refresh, which leaves the old contact at the registrar until it expires.
583    pub fn advance(&mut self) {
584        self.cseq = self.cseq.saturating_add(1);
585    }
586}
587
588/// Read what a registrar said.
589///
590/// Takes the whole [`Registration`] rather than the two or three fields it happens to need
591/// today. A 2xx lists every binding for the address of record, so almost everything read out of
592/// one is read *relative to what this client sent* — its `Contact` to find its own row, its
593/// instance ID to find the GRUUs minted for it — and a signature that spells those out
594/// positionally grows a new argument every time the answer says something more.
595#[must_use]
596pub fn interpret(response: &Response, registration: &Registration) -> Outcome {
597    let status = response.status.code();
598    let contact = registration.contact();
599
600    if (200..300).contains(&status) {
601        // The registrar's number wins. Refreshing on our own instead is how a client
602        // de-registers itself on every cycle.
603        let granted = granted_expiry(response, &contact).unwrap_or(registration.expires);
604        // Returned even when this side never offered `path`. A registrar that adds one anyway
605        // is doing something worth seeing rather than something to drop on the floor: the
606        // whole security value §5.1 claims for the header is that the UA can look at it.
607        return Outcome::Registered(Box::new(Registered {
608            lease: Lease::from_granted(granted),
609            observation: RegistrationObservation::from_response(response),
610            path: PathSet::from_response(response),
611            service_route: ServiceRoute::from_response(response),
612            flow_accepted: crate::outbound::accepted(response),
613            flow_timer: crate::outbound::flow_timer(response),
614            // Read only when this registration named an instance. §4.2 pairs the GRUUs with the
615            // `+sip.instance` they were minted for, and with no instance to match there is no
616            // row that is ours — only other devices'.
617            gruus: registration
618                .instance
619                .as_ref()
620                .map_or_else(Gruus::default, |instance| {
621                    Gruus::from_response(response, instance)
622                }),
623            // Read whether or not this side asked for push, for the reason `path` is: a registrar
624            // that volunteers a `Feature-Caps` is telling us something, and §8.2's whole value is
625            // that a client can compare what it named against what the registrar can use.
626            push: Support::from_response(response),
627        }));
628    }
629
630    // §8.1, and it is checked before the challenge codes because it is not about this attempt:
631    // no credential and no retry makes a push service the registrar cannot use usable.
632    //
633    // Only when this registration actually named one. §8.1 defines 555 as the answer to a
634    // request whose push parameters the registrar cannot use, and every piece of advice the
635    // variant carries — retrying will not help, register without push or with a service the
636    // registrar names — is advice to a client that asked for push. To a client that sent no
637    // `pn-*` parameters the same code is a refusal this side has no reading of, and the honest
638    // report of that is the number, through the ordinary rejection below.
639    if status == sipx_sip::push::NOT_SUPPORTED && registration.push.is_some() {
640        return Outcome::PushNotSupported {
641            reason: String::from_utf8_lossy(&response.reason).into_owned(),
642        };
643    }
644
645    if status == 401 || status == 407 {
646        let from_proxy = status == 407;
647        let header = if from_proxy {
648            HeaderName::ProxyAuthenticate
649        } else {
650            HeaderName::WwwAuthenticate
651        };
652        let challenges: Vec<Challenge> = response
653            .headers
654            .get_all(&header)
655            .filter_map(|h| Challenge::parse(&h.value(), from_proxy))
656            .collect();
657        if let Some(challenge) = strongest(challenges) {
658            return Outcome::Challenged(Box::new(challenge));
659        }
660    }
661
662    Outcome::Rejected {
663        status,
664        reason: String::from_utf8_lossy(&response.reason).into_owned(),
665    }
666}
667
668/// The lease the registrar granted to *this client's* binding.
669///
670/// RFC 3261 §10.3 step 8: the 200 lists every current binding for the address of record,
671/// not just the one refreshed, so per §10.2.4 the client finds its own by URI comparison
672/// (§19.1.4) and takes the expiry from that row. The first row may be another device on
673/// another lease. Only when no row is ours does the `Expires` header speak — it is the
674/// per-contact parameter that is per-binding, not the header.
675fn granted_expiry(response: &Response, contact: &str) -> Option<Duration> {
676    if let Ok(own) = Address::parse(contact.as_bytes(), "Contact") {
677        for value in response.headers.typed_all::<ContactValue>() {
678            let Ok(ContactValue::Address(address)) = value else {
679                continue;
680            };
681            if !address.uri.equivalent(&own.uri) {
682                continue;
683            }
684            if let Some(seconds) = contact_expires(&address) {
685                return Some(Duration::from_secs(seconds));
686            }
687            // Our binding, listed without a per-contact expiry: the header applies to it.
688            break;
689        }
690    }
691    response
692        .headers
693        .value(&HeaderName::Expires)
694        .and_then(|value| {
695            std::str::from_utf8(&value)
696                .ok()
697                .and_then(|text| text.trim().parse::<u64>().ok())
698        })
699        .map(Duration::from_secs)
700}
701
702fn contact_expires(address: &Address) -> Option<u64> {
703    let value = address.param("expires")?;
704    std::str::from_utf8(value).ok()?.trim().parse().ok()
705}
706
707/// Add credentials answering a challenge to a request.
708pub fn authorize(
709    request: &mut Request,
710    challenge: &Challenge,
711    credentials: &Credentials,
712    nonce_count: u32,
713) -> Result<(), sipx_sip::error::BuildError> {
714    let uri = String::from_utf8_lossy(&request.uri.to_bytes()).into_owned();
715    let method = String::from_utf8_lossy(request.method.as_bytes()).into_owned();
716    let value = respond(
717        challenge,
718        credentials,
719        &method,
720        &uri,
721        nonce_count,
722        &new_cnonce(),
723    );
724    let header = sipx_sip::Header::build(challenge.response_header(), Bytes::from(value))?;
725    request.headers.push(header);
726    Ok(())
727}
728
729#[cfg(test)]
730#[allow(
731    clippy::unwrap_used,
732    clippy::expect_used,
733    clippy::panic,
734    clippy::indexing_slicing
735)]
736mod tests {
737    use super::*;
738    use sipx_sip::{Host, HostName, Limits, Message, parse_datagram};
739
740    /// The `Contact` this client registers in every test here.
741    const CONTACT: &str = "<sip:alice@192.0.2.5:5060>";
742
743    fn registration() -> Registration {
744        Registration {
745            registrar: Uri::sip(Host::Name(HostName::new("example.com").expect("valid"))),
746            aor: "<sip:alice@example.com>".to_owned(),
747            contact: CONTACT.to_owned(),
748            expires: Duration::from_secs(3600),
749            call_id: "reg-1@192.0.2.5".to_owned(),
750            cseq: 1,
751            instance: None,
752            reg_id: None,
753            gruu: None,
754            push: None,
755            headers: Vec::new(),
756        }
757    }
758
759    /// The same registration, presenting an instance and asking for a GRUU.
760    fn with_gruu() -> Registration {
761        Registration {
762            instance: Some(instance()),
763            gruu: Some(gruu::Kind::Public),
764            ..registration()
765        }
766    }
767
768    fn instance() -> InstanceId {
769        InstanceId::parse("urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6").expect("a urn")
770    }
771
772    fn response(text: &str) -> Response {
773        match parse_datagram(Bytes::from(text.to_owned()), &Limits::datagram()).expect("parses") {
774            Message::Response(r) => r,
775            Message::Request(_) => panic!("a response"),
776        }
777    }
778
779    fn ok_with(extra: &str) -> Response {
780        response(&format!(
781            "SIP/2.0 200 OK\r\n\
782             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
783             To: <sip:alice@example.com>;tag=r\r\n\
784             From: <sip:alice@example.com>;tag=1\r\n\
785             Call-ID: reg-1@192.0.2.5\r\n\
786             CSeq: 1 REGISTER\r\n\
787             {extra}\
788             Content-Length: 0\r\n\r\n"
789        ))
790    }
791
792    fn observation_from(via: Option<&str>) -> RegistrationObservation {
793        let via = via.map_or_else(String::new, |value| format!("Via: {value}\r\n"));
794        let outcome = interpret(
795            &response(&format!(
796                "SIP/2.0 200 OK\r\n\
797                 {via}\
798                 To: <sip:alice@example.com>;tag=r\r\n\
799                 From: <sip:alice@example.com>;tag=1\r\n\
800                 Call-ID: reg-1@192.0.2.5\r\n\
801                 CSeq: 1 REGISTER\r\n\
802                 Content-Length: 0\r\n\r\n"
803            )),
804            &registration(),
805        );
806        let Outcome::Registered(registered) = outcome else {
807            panic!("expected successful registration, got {outcome:?}");
808        };
809        registered.observation
810    }
811
812    #[test]
813    fn registration_observation_vectors_are_typed_and_non_authoritative() {
814        assert_eq!(
815            observation_from(Some("SIP/2.0/UDP private.example:5060;branch=z9hG4bKx")),
816            RegistrationObservation::Absent
817        );
818        assert_eq!(
819            observation_from(Some(
820                "SIP/2.0/UDP private.example:5060;received=203.0.113.9;rport=41234;branch=z9hG4bKx"
821            )),
822            RegistrationObservation::Observed("203.0.113.9:41234".parse().expect("address"))
823        );
824        assert_eq!(
825            observation_from(Some(
826                "SIP/2.0/TCP private.example:5060;received=[2001:db8::9];rport=5060;branch=z9hG4bKx"
827            )),
828            RegistrationObservation::Observed("[2001:db8::9]:5060".parse().expect("address"))
829        );
830        for (via, error) in [
831            (None, RegistrationObservationError::MissingVia),
832            (
833                Some("not-a-via"),
834                RegistrationObservationError::MalformedVia,
835            ),
836            (
837                Some("SIP/2.0/UDP h;rport=41234;branch=z9hG4bKx"),
838                RegistrationObservationError::MissingReceived,
839            ),
840            (
841                Some("SIP/2.0/UDP h;received=203.0.113.9;branch=z9hG4bKx"),
842                RegistrationObservationError::MissingRport,
843            ),
844            (
845                Some("SIP/2.0/UDP h;received=203.0.113.9;rport;branch=z9hG4bKx"),
846                RegistrationObservationError::MissingRport,
847            ),
848            (
849                Some("SIP/2.0/UDP h;received=registrar.example;rport=5060;branch=z9hG4bKx"),
850                RegistrationObservationError::NonIpReceived,
851            ),
852            (
853                Some("SIP/2.0/UDP h;received;rport=5060;branch=z9hG4bKx"),
854                RegistrationObservationError::NonIpReceived,
855            ),
856            (
857                Some("SIP/2.0/UDP h;received=203.0.113.9;rport=nope;branch=z9hG4bKx"),
858                RegistrationObservationError::InvalidRport,
859            ),
860            (
861                Some("SIP/2.0/UDP h;received=203.0.113.9;rport=+5060;branch=z9hG4bKx"),
862                RegistrationObservationError::InvalidRport,
863            ),
864            (
865                Some("SIP/2.0/UDP h;received=203.0.113.9;rport=0;branch=z9hG4bKx"),
866                RegistrationObservationError::InvalidRport,
867            ),
868            (
869                Some(
870                    "SIP/2.0/UDP h;received=203.0.113.9;Received=203.0.113.10;rport=5060;branch=z9hG4bKx",
871                ),
872                RegistrationObservationError::ContradictoryReceived,
873            ),
874            (
875                Some("SIP/2.0/UDP h;received=203.0.113.9;rport=5060;RPORT=5061;branch=z9hG4bKx"),
876                RegistrationObservationError::ContradictoryRport,
877            ),
878        ] {
879            assert_eq!(
880                observation_from(via),
881                RegistrationObservation::Invalid(error)
882            );
883        }
884    }
885
886    #[test]
887    fn only_the_top_via_hop_contributes_the_observation() {
888        assert_eq!(
889            observation_from(Some(
890                "SIP/2.0/UDP top.example:5060;received=203.0.113.9;rport=41234;branch=z9hG4bKtop, \
891                 SIP/2.0/UDP lower.example:5060;received=not-an-ip;rport=0;branch=z9hG4bKlower"
892            )),
893            RegistrationObservation::Observed("203.0.113.9:41234".parse().expect("address")),
894            "a malformed lower hop cannot contaminate the top-hop observation"
895        );
896        assert_eq!(
897            observation_from(Some(
898                "SIP/2.0/UDP top.example:5060;received=203.0.113.9;rport=41234;branch=z9hG4bKtop, \
899                 SIP/2.0/UDP lower.example:5060;opaque=\"unterminated"
900            )),
901            RegistrationObservation::Observed("203.0.113.9:41234".parse().expect("address")),
902            "even invalid quoted syntax in a lower hop is outside this observation"
903        );
904
905        let outcome = interpret(
906            &ok_with(
907                "Via: SIP/2.0/UDP lower.example:5060;received=198.51.100.7;rport=50999;branch=z9hG4bKlower\r\n",
908            ),
909            &registration(),
910        );
911        let Outcome::Registered(registered) = outcome else {
912            panic!("expected successful registration, got {outcome:?}");
913        };
914        assert_eq!(
915            registered.observation,
916            RegistrationObservation::Absent,
917            "a lower Via row cannot supply parameters missing from the top row"
918        );
919    }
920
921    #[test]
922    fn invalid_observation_does_not_replace_registration_results() {
923        let instance = instance();
924        let urn = instance.urn().to_owned();
925        let registration = Registration {
926            instance: Some(instance),
927            reg_id: RegId::new(1),
928            gruu: Some(gruu::Kind::Public),
929            push: Some(
930                sipx_sip::push::Device::new("webpush", "c1a5b3e7d9f2").expect("valid push device"),
931            ),
932            ..registration()
933        };
934        let returned_contact = format!(
935            "{};pub-gruu=\"sip:alice@example.com;gr={urn}\"\
936             ;temp-gruu=\"sip:t7k2xq9f4m@example.com;gr\";expires=600",
937            registration.contact()
938        );
939        let outcome = interpret(
940            &response(&format!(
941                "SIP/2.0 200 OK\r\n\
942                 Via: SIP/2.0/UDP private.example:5060;received=not-an-ip;rport=41234;branch=z9hG4bKx\r\n\
943                 To: <sip:alice@example.com>;tag=r\r\n\
944                 From: <sip:alice@example.com>;tag=1\r\n\
945                 Call-ID: reg-1@192.0.2.5\r\n\
946                 CSeq: 1 REGISTER\r\n\
947                 Contact: {returned_contact}\r\n\
948                 Path: <sip:path.example;lr>\r\n\
949                 Service-Route: <sip:route.example;lr>\r\n\
950                 Require: outbound\r\n\
951                 Flow-Timer: 25\r\n\
952                 Feature-Caps: *;+sip.pns=\"webpush\";+sip.pnsreg=\"120\"\
953                 ;+sip.pnspurr=\"opaque-purr-1\"\r\n\
954                 Content-Length: 0\r\n\r\n"
955            )),
956            &registration,
957        );
958        let Outcome::Registered(registered) = outcome else {
959            panic!("an invalid observation must not reject the registration");
960        };
961        assert_eq!(
962            registered.observation,
963            RegistrationObservation::Invalid(RegistrationObservationError::NonIpReceived)
964        );
965        assert_eq!(registered.lease.granted, Duration::from_secs(600));
966        assert_eq!(
967            registered.path.rendered(),
968            vec!["<sip:path.example;lr>".to_owned()]
969        );
970        assert_eq!(
971            registered.service_route.rendered(),
972            vec!["<sip:route.example;lr>".to_owned()]
973        );
974        assert!(registered.flow_accepted);
975        assert_eq!(registered.flow_timer, Some(Duration::from_secs(25)));
976        assert_eq!(
977            registered.gruus.public().map(sipx_sip::Uri::to_string),
978            Some(format!("sip:alice@example.com;gr={urn}"))
979        );
980        assert_eq!(
981            registered.gruus.temporary().map(sipx_sip::Uri::to_string),
982            Some("sip:t7k2xq9f4m@example.com;gr".to_owned())
983        );
984        assert!(registered.push.supports("webpush"));
985        assert!(registered.push.refreshes_required());
986        assert_eq!(
987            registered.push.refresh_interval(),
988            Some(Duration::from_secs(120))
989        );
990        assert_eq!(registered.push.purr(), Some("opaque-purr-1"));
991    }
992
993    /// The story's failing-first test.
994    #[test]
995    fn a_registration_preserves_the_path_it_was_returned() {
996        // Two proxies, on separate rows — which is how they actually arrive, each one having
997        // pushed itself onto the front on the way through (RFC 3327 §5.2).
998        let outcome = interpret(
999            &ok_with(
1000                "Path: <sip:edge.example.com;lr>\r\nPath: <sip:core.example.net;lr>\r\nContact: <sip:alice@192.0.2.5:5060>;expires=600\r\n",
1001            ),
1002            &registration(),
1003        );
1004        let Outcome::Registered(registered) = outcome else {
1005            panic!("expected a registration, got {outcome:?}");
1006        };
1007        assert_eq!(registered.lease.granted, Duration::from_secs(600));
1008        assert_eq!(
1009            registered.path.rendered(),
1010            vec![
1011                "<sip:edge.example.com;lr>".to_owned(),
1012                "<sip:core.example.net;lr>".to_owned()
1013            ],
1014            "the path vector was lost, reordered, or flattened"
1015        );
1016    }
1017
1018    #[test]
1019    fn a_comma_joined_path_is_the_same_as_separate_rows() {
1020        // RFC 3261 §7.3 makes these two spellings equivalent for a list header, and a path
1021        // vector read a line at a time turns two hops into one opaque string — losing the
1022        // order, which is the entire content of the vector.
1023        let joined = interpret(
1024            &ok_with("Path: <sip:edge.example.com;lr>, <sip:core.example.net;lr>\r\n"),
1025            &registration(),
1026        );
1027        let separate = interpret(
1028            &ok_with("Path: <sip:edge.example.com;lr>\r\nPath: <sip:core.example.net;lr>\r\n"),
1029            &registration(),
1030        );
1031        match (joined, separate) {
1032            (Outcome::Registered(one), Outcome::Registered(other)) => {
1033                assert_eq!(
1034                    one.path.rendered().len(),
1035                    2,
1036                    "the comma-joined row was not split"
1037                );
1038                assert_eq!(one.path, other.path);
1039            }
1040            other => panic!("expected two registrations, got {other:?}"),
1041        }
1042    }
1043
1044    #[test]
1045    fn a_path_parameter_survives_because_outbound_will_need_it() {
1046        // RFC 5626 §5.3 hangs the `ob` marker off a Path value. A vector kept as bare URIs
1047        // would parse cleanly and be quietly useless to T-15.
1048        let outcome = interpret(
1049            &ok_with("Path: <sip:edge.example.com;lr;ob>\r\n"),
1050            &registration(),
1051        );
1052        let Outcome::Registered(registered) = outcome else {
1053            panic!("expected a registration");
1054        };
1055        assert!(
1056            registered
1057                .path
1058                .hops()
1059                .first()
1060                .expect("one hop")
1061                .uri
1062                .params()
1063                .is_some_and(|params| params.contains("ob")),
1064            "the ob parameter was dropped: {:?}",
1065            registered.path.rendered()
1066        );
1067    }
1068
1069    #[test]
1070    fn a_register_offers_the_path_option_tag() {
1071        // RFC 3327 §5.2 tells proxies not to add themselves unless the UA has indicated
1072        // support, so a UA that stays quiet here is unreachable from behind exactly the
1073        // proxies the mechanism exists to traverse.
1074        let request = registration().request().expect("builds");
1075        let supported = request
1076            .headers
1077            .value(&HeaderName::Supported)
1078            .expect("Supported is present");
1079        assert!(String::from_utf8_lossy(&supported).contains("path"));
1080    }
1081
1082    /// RFC 5627 §4.1: the option tag is a MUST, and the instance ID is what the GRUU will be
1083    /// minted for. Without either, §5.2 has the registrar attach nothing and the mechanism is
1084    /// simply not in play — silently, which is the part that costs an afternoon.
1085    #[test]
1086    fn a_register_asking_for_a_gruu_offers_the_tag_and_names_the_instance() {
1087        let request = with_gruu().request().expect("builds");
1088        let supported = request
1089            .headers
1090            .value(&HeaderName::Supported)
1091            .expect("Supported is present");
1092        assert!(String::from_utf8_lossy(&supported).contains("gruu"));
1093        let contact = request
1094            .headers
1095            .value(&HeaderName::Contact)
1096            .expect("a Contact");
1097        assert_eq!(
1098            String::from_utf8_lossy(&contact),
1099            format!("{CONTACT};+sip.instance=\"<{}>\"", instance().urn())
1100        );
1101    }
1102
1103    /// The story's other half of the same rule: **two mechanisms, one instance identity.**
1104    ///
1105    /// RFC 5626 §4.1 and RFC 5627 §4.1 name the same `+sip.instance` tag. A `Contact` presenting
1106    /// it twice — or presenting two different URNs — is a device asking a registrar that
1107    /// correlates the two mechanisms to treat it as two devices, and the symptom appears at the
1108    /// registrar rather than here.
1109    #[test]
1110    fn outbound_and_gruu_present_one_instance_identity_between_them() {
1111        let both = Registration {
1112            reg_id: Some(RegId::new(2).expect("valid")),
1113            ..with_gruu()
1114        };
1115        let request = both.request().expect("builds");
1116        let contact = request
1117            .headers
1118            .value(&HeaderName::Contact)
1119            .expect("a Contact");
1120        let contact = String::from_utf8_lossy(&contact);
1121        assert_eq!(
1122            contact.matches("+sip.instance").count(),
1123            1,
1124            "the instance identity appeared more than once: {contact}"
1125        );
1126        assert!(contact.contains(&format!("+sip.instance=\"<{}>\"", instance().urn())));
1127        assert!(contact.contains(";reg-id=2"), "{contact}");
1128
1129        let supported = request
1130            .headers
1131            .value(&HeaderName::Supported)
1132            .expect("a Supported");
1133        let supported = String::from_utf8_lossy(&supported);
1134        assert_eq!(supported, "path, outbound, gruu");
1135    }
1136
1137    /// Offering a tag for a mechanism defined in terms of an instance ID the request never sends
1138    /// asks the registrar an incoherent question, and the rejection that comes back reads like a
1139    /// credentials problem.
1140    #[test]
1141    fn neither_mechanism_is_offered_without_an_instance_to_name() {
1142        let confused = Registration {
1143            instance: None,
1144            reg_id: RegId::new(1),
1145            gruu: Some(gruu::Kind::Public),
1146            ..registration()
1147        };
1148        let request = confused.request().expect("builds");
1149        let supported = request
1150            .headers
1151            .value(&HeaderName::Supported)
1152            .expect("a Supported");
1153        assert_eq!(String::from_utf8_lossy(&supported), "path");
1154        assert_eq!(
1155            request
1156                .headers
1157                .value(&HeaderName::Contact)
1158                .expect("a Contact")
1159                .as_ref(),
1160            CONTACT.as_bytes()
1161        );
1162    }
1163
1164    /// The GRUUs a registrar issues travel with the binding they were minted for (§4.2).
1165    #[test]
1166    fn a_registration_keeps_the_gruus_the_registrar_issued() {
1167        let urn = instance().urn().to_owned();
1168        let outcome = interpret(
1169            &ok_with(&format!(
1170                "Contact: <sip:alice@192.0.2.5:5060>;+sip.instance=\"<{urn}>\"\
1171                 ;pub-gruu=\"sip:alice@example.com;gr={urn}\"\
1172                 ;temp-gruu=\"sip:t7k2xq9f4m@example.com;gr\";expires=600\r\n"
1173            )),
1174            &with_gruu(),
1175        );
1176        let Outcome::Registered(registered) = outcome else {
1177            panic!("expected a registration");
1178        };
1179        assert_eq!(
1180            registered.gruus.public().map(sipx_sip::Uri::to_string),
1181            Some(format!("sip:alice@example.com;gr={urn}"))
1182        );
1183        assert_eq!(
1184            registered.gruus.temporary().map(sipx_sip::Uri::to_string),
1185            Some("sip:t7k2xq9f4m@example.com;gr".to_owned())
1186        );
1187    }
1188
1189    /// A registration that never named an instance has no row of its own to read GRUUs from, and
1190    /// the rows that are there belong to other devices.
1191    #[test]
1192    fn a_registration_without_an_instance_adopts_no_gruus() {
1193        let outcome = interpret(
1194            &ok_with(
1195                "Contact: <sip:alice@198.51.100.9:5060>\
1196                 ;+sip.instance=\"<urn:uuid:00000000-0000-4000-8000-000000000000>\"\
1197                 ;pub-gruu=\"sip:alice@example.com;gr=urn:uuid:00000000-0000-4000-8000-000000000000\"\r\n",
1198            ),
1199            &registration(),
1200        );
1201        let Outcome::Registered(registered) = outcome else {
1202            panic!("expected a registration");
1203        };
1204        assert!(registered.gruus.is_empty());
1205    }
1206
1207    #[test]
1208    fn a_path_returned_unasked_is_still_reported() {
1209        // §5.1's reason for the header to reach the UA at all: "such inspection might allow
1210        // the UA to detect intermediate proxies that have inappropriately added themselves".
1211        // Dropping it because we did not ask would remove the only defence it offers.
1212        let outcome = interpret(
1213            &ok_with("Path: <sip:stranger.example.org;lr>\r\n"),
1214            &registration(),
1215        );
1216        let Outcome::Registered(registered) = outcome else {
1217            panic!("expected a registration");
1218        };
1219        assert_eq!(
1220            registered.path.hops_outside(&["edge.example.com"]),
1221            vec!["<sip:stranger.example.org;lr>".to_owned()]
1222        );
1223    }
1224
1225    #[test]
1226    fn no_path_is_an_empty_set_rather_than_an_absent_one() {
1227        let outcome = interpret(&ok_with(""), &registration());
1228        let Outcome::Registered(registered) = outcome else {
1229            panic!("expected a registration");
1230        };
1231        assert!(registered.path.is_empty());
1232    }
1233
1234    fn service_route_of(extra: &str) -> ServiceRoute {
1235        let outcome = interpret(&ok_with(extra), &registration());
1236        match outcome {
1237            Outcome::Registered(registered) => registered.service_route,
1238            other => panic!("expected a registration, got {other:?}"),
1239        }
1240    }
1241
1242    /// RFC 3608 §6.1: a UA that exercises the route "MUST preserve the order".
1243    #[test]
1244    fn a_service_route_keeps_the_order_the_registrar_listed() {
1245        let route = service_route_of(
1246            "Service-Route: <sip:edge.example.com;lr>\r\n\
1247             Service-Route: <sip:core.example.net;lr>\r\n",
1248        );
1249        assert_eq!(
1250            route.rendered(),
1251            vec![
1252                "<sip:edge.example.com;lr>".to_owned(),
1253                "<sip:core.example.net;lr>".to_owned(),
1254            ],
1255            "the outbound route set is not in the order it arrived in"
1256        );
1257    }
1258
1259    /// §5's grammar is `sr-value *( COMMA sr-value )`, so the two spellings are one value.
1260    #[test]
1261    fn a_comma_joined_service_route_is_the_same_as_separate_rows() {
1262        let joined = service_route_of(
1263            "Service-Route: <sip:edge.example.com;lr>, <sip:core.example.net;lr>\r\n",
1264        );
1265        let separate = service_route_of(
1266            "Service-Route: <sip:edge.example.com;lr>\r\n\
1267             Service-Route: <sip:core.example.net;lr>\r\n",
1268        );
1269        assert_eq!(joined.hops().len(), 2, "the comma-joined row was not split");
1270        assert_eq!(joined, separate);
1271    }
1272
1273    /// RFC 3608 §6.1: "if there is no Service-Route header field in the response, the UA clears
1274    /// any service route for that address-of-record previously stored".
1275    ///
1276    /// The rule is easy to get backwards — treating an absent header as "nothing to say, keep
1277    /// what you had" leaves a UA routing through a proxy the registrar has stopped naming.
1278    #[test]
1279    fn a_response_without_a_service_route_says_clear_it_rather_than_keep_it() {
1280        assert!(
1281            service_route_of("").is_empty(),
1282            "an absent Service-Route must read as empty, so that storing it clears the old one"
1283        );
1284    }
1285
1286    /// §5: values "MUST include the loose-routing indicator parameter `;lr`".
1287    ///
1288    /// Reported, not enforced: the request still reaches the proxy named, and discarding a whole
1289    /// route set over a missing parameter would make a UA unroutable for an invisible reason.
1290    #[test]
1291    fn a_hop_without_lr_is_reported_rather_than_dropped() {
1292        let route = service_route_of(
1293            "Service-Route: <sip:edge.example.com;lr>\r\n\
1294             Service-Route: <sip:strict.example.net>\r\n",
1295        );
1296        assert_eq!(route.hops().len(), 2, "the offending hop was dropped");
1297        assert_eq!(
1298            route.hops_without_loose_routing(),
1299            vec!["<sip:strict.example.net>".to_owned()],
1300            "the hop missing ;lr was not reported"
1301        );
1302    }
1303
1304    /// The two vectors travel in opposite directions and must not be read from each other's
1305    /// header. A registrar that returns only a `Path` has dictated no outbound route.
1306    #[test]
1307    fn a_path_is_not_a_service_route() {
1308        let outcome = interpret(
1309            &ok_with("Path: <sip:edge.example.com;lr>\r\n"),
1310            &registration(),
1311        );
1312        let Outcome::Registered(registered) = outcome else {
1313            panic!("expected a registration");
1314        };
1315        assert!(!registered.path.is_empty(), "the Path was lost");
1316        assert!(
1317            registered.service_route.is_empty(),
1318            "a Path was read as a Service-Route; the UA would route its own requests through \
1319             proxies that only asked to be on the inbound path"
1320        );
1321    }
1322
1323    #[test]
1324    fn the_request_uri_names_the_registrar_and_the_to_names_the_user() {
1325        let request = registration().request().expect("builds");
1326        assert_eq!(request.uri.to_bytes().as_ref(), b"sip:example.com");
1327        assert_eq!(
1328            request
1329                .headers
1330                .value(&HeaderName::To)
1331                .expect("a To")
1332                .as_ref(),
1333            b"<sip:alice@example.com>"
1334        );
1335    }
1336
1337    /// The registrar's number wins. A client that refreshes on the interval it *asked* for
1338    /// de-registers itself every cycle when the registrar grants less.
1339    #[test]
1340    fn the_granted_expiry_overrides_what_was_asked_for() {
1341        let outcome = interpret(
1342            &ok_with("Contact: <sip:alice@192.0.2.5:5060>;expires=60\r\n"),
1343            &registration(),
1344        );
1345        match outcome {
1346            Outcome::Registered(r) => assert_eq!(r.lease.granted, Duration::from_secs(60)),
1347            other => panic!("expected a lease, got {other:?}"),
1348        }
1349    }
1350
1351    /// A per-contact expiry beats the `Expires` header, which applies to all of them.
1352    #[test]
1353    fn a_contact_expiry_beats_the_expires_header() {
1354        let outcome = interpret(
1355            &ok_with("Expires: 3600\r\nContact: <sip:alice@192.0.2.5:5060>;expires=120\r\n"),
1356            &registration(),
1357        );
1358        match outcome {
1359            Outcome::Registered(r) => assert_eq!(r.lease.granted, Duration::from_secs(120)),
1360            other => panic!("expected a lease, got {other:?}"),
1361        }
1362    }
1363
1364    /// RFC 3261 §10.3 step 8: the 200 lists every current binding for the address of
1365    /// record, and §10.2.4 has the client find its own by URI comparison (§19.1.4). Another
1366    /// device's binding listed first must not become this client's refresh schedule — a
1367    /// lease scheduled off the wrong row lapses while the client still believes it holds it.
1368    #[test]
1369    fn the_expiry_comes_from_our_own_binding_not_the_first_listed() {
1370        let outcome = interpret(
1371            &ok_with(
1372                "Contact: <sip:alice@198.51.100.9:5060>;expires=3600\r\n\
1373                 Contact: <sip:alice@192.0.2.5:5060>;expires=60\r\n",
1374            ),
1375            &registration(),
1376        );
1377        match outcome {
1378            Outcome::Registered(r) => assert_eq!(r.lease.granted, Duration::from_secs(60)),
1379            other => panic!("expected a lease, got {other:?}"),
1380        }
1381    }
1382
1383    #[test]
1384    fn the_expires_header_is_used_when_the_contact_has_none() {
1385        let outcome = interpret(&ok_with("Expires: 300\r\n"), &registration());
1386        match outcome {
1387            Outcome::Registered(r) => assert_eq!(r.lease.granted, Duration::from_secs(300)),
1388            other => panic!("expected a lease, got {other:?}"),
1389        }
1390    }
1391
1392    /// The refresh must leave room to retry. Refreshing exactly at expiry means a single lost
1393    /// packet drops the registration.
1394    #[test]
1395    fn the_refresh_leaves_margin_before_the_lease_ends() {
1396        let lease = Lease::from_granted(Duration::from_secs(3600));
1397        assert_eq!(lease.refresh_after, Duration::from_secs(3240));
1398        assert!(lease.refresh_after < lease.granted);
1399
1400        // And a short lease still leaves something.
1401        let short = Lease::from_granted(Duration::from_secs(15));
1402        assert!(short.refresh_after < short.granted);
1403        assert!(short.refresh_after >= Duration::from_secs(1));
1404
1405        // Even a degenerate one-second lease must not schedule a refresh at zero, which would
1406        // spin.
1407        let degenerate = Lease::from_granted(Duration::from_secs(1));
1408        assert_eq!(degenerate.refresh_after, Duration::from_secs(1));
1409    }
1410
1411    #[test]
1412    fn a_401_is_a_challenge_rather_than_a_failure() {
1413        let challenged = response(
1414            "SIP/2.0 401 Unauthorized\r\n\
1415             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
1416             To: <sip:alice@example.com>;tag=r\r\n\
1417             From: <sip:alice@example.com>;tag=1\r\n\
1418             Call-ID: reg-1@192.0.2.5\r\n\
1419             CSeq: 1 REGISTER\r\n\
1420             WWW-Authenticate: Digest realm=\"example.com\", nonce=\"abc\", qop=\"auth\"\r\n\
1421             Content-Length: 0\r\n\r\n",
1422        );
1423        match interpret(&challenged, &registration()) {
1424            Outcome::Challenged(challenge) => {
1425                assert_eq!(challenge.realm, "example.com");
1426                assert!(challenge.qop_auth);
1427                assert!(!challenge.from_proxy);
1428            }
1429            other => panic!("expected a challenge, got {other:?}"),
1430        }
1431    }
1432
1433    #[test]
1434    fn a_407_is_a_proxy_challenge_and_answered_in_the_proxy_header() {
1435        let challenged = response(
1436            "SIP/2.0 407 Proxy Authentication Required\r\n\
1437             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
1438             To: <sip:alice@example.com>;tag=r\r\n\
1439             From: <sip:alice@example.com>;tag=1\r\n\
1440             Call-ID: reg-1@192.0.2.5\r\n\
1441             CSeq: 1 REGISTER\r\n\
1442             Proxy-Authenticate: Digest realm=\"p\", nonce=\"n\"\r\n\
1443             Content-Length: 0\r\n\r\n",
1444        );
1445        match interpret(&challenged, &registration()) {
1446            Outcome::Challenged(challenge) => {
1447                assert!(challenge.from_proxy);
1448                assert_eq!(challenge.response_header(), HeaderName::ProxyAuthorization);
1449            }
1450            other => panic!("expected a challenge, got {other:?}"),
1451        }
1452    }
1453
1454    #[test]
1455    fn a_403_is_a_rejection_not_a_challenge() {
1456        let refused = response(
1457            "SIP/2.0 403 Forbidden\r\n\
1458             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
1459             To: <sip:alice@example.com>;tag=r\r\n\
1460             From: <sip:alice@example.com>;tag=1\r\n\
1461             Call-ID: reg-1@192.0.2.5\r\n\
1462             CSeq: 1 REGISTER\r\n\
1463             Content-Length: 0\r\n\r\n",
1464        );
1465        match interpret(&refused, &registration()) {
1466            Outcome::Rejected { status, reason } => {
1467                assert_eq!(status, 403);
1468                assert_eq!(reason, "Forbidden");
1469            }
1470            other => panic!("expected a rejection, got {other:?}"),
1471        }
1472    }
1473
1474    fn refused_555() -> Response {
1475        response(
1476            "SIP/2.0 555 Push Notification Service Not Supported\r\n\
1477             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
1478             To: <sip:alice@example.com>;tag=r\r\n\
1479             From: <sip:alice@example.com>;tag=1\r\n\
1480             Call-ID: reg-1@192.0.2.5\r\n\
1481             CSeq: 1 REGISTER\r\n\
1482             Content-Length: 0\r\n\r\n",
1483        )
1484    }
1485
1486    /// RFC 8599 §8.1's 555, to a registration that named a push service: its own outcome, because
1487    /// no credential and no retry makes that service usable at this registrar.
1488    #[test]
1489    fn a_555_to_a_push_registration_is_its_own_outcome_rather_than_a_number() {
1490        let asking = Registration {
1491            push: Some(sipx_sip::push::Device::new("webpush", "c1a5b3e7d9f2").expect("valid")),
1492            ..registration()
1493        };
1494        match interpret(&refused_555(), &asking) {
1495            Outcome::PushNotSupported { reason } => {
1496                assert_eq!(reason, sipx_sip::push::NOT_SUPPORTED_REASON);
1497            }
1498            other => panic!("expected §8.1's own outcome, got {other:?}"),
1499        }
1500    }
1501
1502    /// The same code to a registration that named no push service at all. Every piece of advice
1503    /// `PushNotSupported` carries — retrying will not help, register without push — is advice to
1504    /// a client that asked for push, so to this one the honest report is the number.
1505    #[test]
1506    fn a_555_to_a_registration_that_asked_for_no_push_is_an_ordinary_rejection() {
1507        match interpret(&refused_555(), &registration()) {
1508            Outcome::Rejected { status, reason } => {
1509                assert_eq!(status, 555);
1510                assert_eq!(reason, sipx_sip::push::NOT_SUPPORTED_REASON);
1511            }
1512            other => panic!("expected an ordinary rejection, got {other:?}"),
1513        }
1514    }
1515
1516    /// A 401 whose challenge cannot be parsed is a rejection, not a challenge to answer. The
1517    /// alternative is retrying forever against a header we do not understand.
1518    #[test]
1519    fn a_401_with_an_unusable_challenge_is_a_rejection() {
1520        let bad = response(
1521            "SIP/2.0 401 Unauthorized\r\n\
1522             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
1523             To: <sip:alice@example.com>;tag=r\r\n\
1524             From: <sip:alice@example.com>;tag=1\r\n\
1525             Call-ID: reg-1@192.0.2.5\r\n\
1526             CSeq: 1 REGISTER\r\n\
1527             WWW-Authenticate: Basic realm=\"example.com\"\r\n\
1528             Content-Length: 0\r\n\r\n",
1529        );
1530        assert!(matches!(
1531            interpret(&bad, &registration()),
1532            Outcome::Rejected { status: 401, .. }
1533        ));
1534    }
1535
1536    /// A refresh keeps the `Call-ID` and advances the `CSeq`. A new `Call-ID` would leave the
1537    /// old contact registered until it expired on its own.
1538    #[test]
1539    fn a_refresh_keeps_the_call_id_and_advances_the_cseq() {
1540        let mut registration = registration();
1541        let first = registration.request().expect("builds");
1542        registration.advance();
1543        let second = registration.request().expect("builds");
1544
1545        assert_eq!(
1546            first.headers.value(&HeaderName::CallId),
1547            second.headers.value(&HeaderName::CallId),
1548        );
1549        assert_eq!(
1550            second
1551                .headers
1552                .value(&HeaderName::CSeq)
1553                .expect("a CSeq")
1554                .as_ref(),
1555            b"2 REGISTER"
1556        );
1557    }
1558
1559    #[test]
1560    fn application_owned_fields_are_preserved_on_register_refreshes() {
1561        let mut registration = registration();
1562        registration.headers.push(
1563            sipx_sip::Header::build(
1564                HeaderName::Supported,
1565                Bytes::from_static(b"deployment-feature"),
1566            )
1567            .expect("a validated header"),
1568        );
1569        let first = registration.request().expect("builds");
1570        registration.advance();
1571        let second = registration.request().expect("builds");
1572        for request in [first, second] {
1573            assert!(
1574                request
1575                    .headers
1576                    .get_all(&HeaderName::Supported)
1577                    .any(|header| header.raw_value() == b"deployment-feature")
1578            );
1579        }
1580    }
1581
1582    /// The credentials are computed over the Request-URI of the request they authorize.
1583    #[test]
1584    fn authorization_covers_the_request_uri() {
1585        let mut request = registration().request().expect("builds");
1586        let challenge = Challenge::parse(
1587            br#"Digest realm="example.com", nonce="abc", qop="auth""#,
1588            false,
1589        )
1590        .expect("parses");
1591        authorize(
1592            &mut request,
1593            &challenge,
1594            &Credentials::new("alice", "secret"),
1595            1,
1596        )
1597        .expect("authorizes");
1598
1599        let header = request
1600            .headers
1601            .value(&HeaderName::Authorization)
1602            .expect("an Authorization");
1603        let text = String::from_utf8_lossy(&header);
1604        assert!(text.contains(r#"uri="sip:example.com""#), "{text}");
1605        assert!(text.contains(r#"username="alice""#), "{text}");
1606        assert!(text.contains("nc=00000001"), "{text}");
1607    }
1608}