Skip to main content

sipx_ua/
push.rs

1//! Being reachable through a push notification (RFC 8599), from the user agent's side.
2//!
3//! The client this is for holds no connection at all: no socket, no keep-alive, and quite possibly
4//! not running. Every other mechanism in this crate assumes there is *something* the registrar can
5//! route down — Outbound keeps a flow open, GRUU names the instance at the end of one. RFC 8599 is
6//! what is left when there is nothing: the proxy leaves SIP for one hop, asks the client's push
7//! notification service to wake it, and the client goes and gets a flow.
8//!
9//! Three things are worth knowing before reading on.
10//!
11//! - **The push is not the call.** §4.1.3: "When a UA receives a push notification, the UA MUST
12//!   send a binding-refresh REGISTER request." The notification is permission to go and get a
13//!   flow; the request it was sent for arrives down that flow afterwards. A client that waits for
14//!   the INVITE instead of refreshing is waiting on a path that does not exist yet — which is why
15//!   the ordering is a type here ([`Pending`]) rather than a comment.
16//! - **sipx ships no push service.** [`PushService`] is a trait and this repository implements it
17//!   nowhere. sipx is a stack, not a client of anybody's push transport, and the non-goals in
18//!   `docs/vision.md` rule out the alternative. What sipx owes is the SIP half: the parameters,
19//!   the option negotiation, 555, and the refresh ordering.
20//! - **The proxy half is not here.** §5.6 has a proxy hold the request in a bucket while the
21//!   client wakes, and §4.2's registrar behaviour mints the PURR. Both are roles sipx does not
22//!   play, and neither shares anything with this but the wire format.
23
24use std::time::Duration;
25
26use sipx_sip::Response;
27use sipx_sip::error::BuildError;
28use sipx_sip::push::{Device, Indicators};
29
30pub use sipx_sip::push::{NOT_SUPPORTED, NOT_SUPPORTED_REASON};
31
32/// The push notification service a device can be woken through (§3).
33///
34/// **sipx implements this nowhere, and that is deliberate.** Waking a device means speaking some
35/// vendor's HTTP API over some vendor's credentials, on a schedule that vendor sets — none of
36/// which is SIP, and all of which would date faster than the rest of this crate. What sipx needs
37/// from a push service is three strings, and this is them.
38///
39/// An implementation is an adapter the application writes over whatever it already uses to reach
40/// its push service. The tests use a stub for the same reason: there is nothing here that a real
41/// implementation would exercise differently.
42pub trait PushService {
43    /// The `pn-provider` value naming this service (§8.7).
44    ///
45    /// A value from the registry §8.8 creates. It is the name the *registrar* has to recognise, so
46    /// inventing one produces a binding nothing will ever wake — see [`Support::supports`].
47    fn provider(&self) -> &str;
48
49    /// The `pn-prid` value: the identifier this service knows the device by (§8.7).
50    fn prid(&self) -> &str;
51
52    /// The `pn-param` value, when the service needs one (§8.7).
53    ///
54    /// Service-specific and not SIP's business, which is why the default is `None`.
55    fn param(&self) -> Option<&str> {
56        None
57    }
58
59    /// The parameters a REGISTER's `Contact` URI must carry to name this service (§4.1.2).
60    ///
61    /// Fails when one of the three values is not something a URI parameter can hold; see
62    /// [`Device`] for why that is checked here rather than discovered at the registrar.
63    fn device(&self) -> Result<Device, BuildError> {
64        let device = Device::new(self.provider(), self.prid())?;
65        match self.param() {
66            Some(param) => device.with_param(param),
67            None => Ok(device),
68        }
69    }
70}
71
72/// What a registrar said about push, read from the `Feature-Caps` of a REGISTER response (§8.2).
73///
74/// The interesting question this answers is not "did the registration succeed" — it did, or there
75/// would be no response to read. It is **"can this registrar actually wake me"**, and the two come
76/// apart: a registrar that supports some other push service answers 200 and records a perfectly
77/// good binding that nothing will ever ring. That failure looks exactly like success from every
78/// angle except this one.
79#[derive(Debug, Clone, Default, PartialEq, Eq)]
80pub struct Support {
81    services: Vec<String>,
82    refreshes_required: bool,
83    refresh_interval: Option<Duration>,
84    purr: Option<String>,
85}
86
87impl Support {
88    /// Read what a REGISTER response said about push (§8.2).
89    ///
90    /// Every `Feature-Caps` row is read, because a registrar offering several push services says
91    /// so with several indicators and a client that stopped at the first would miss its own.
92    #[must_use]
93    pub fn from_response(response: &Response) -> Self {
94        let mut support = Self::default();
95        for value in response.headers.typed_all::<Indicators>() {
96            let Ok(indicators) = value else {
97                continue;
98            };
99            if let Some(pns) = indicators.pns() {
100                support
101                    .services
102                    .push(String::from_utf8_lossy(pns).into_owned());
103            }
104            if indicators.refreshes_required() {
105                support.refreshes_required = true;
106                support.refresh_interval = support
107                    .refresh_interval
108                    .or_else(|| indicators.refresh_interval());
109            }
110            if let Some(purr) = indicators.purr() {
111                support.purr = Some(String::from_utf8_lossy(purr).into_owned());
112            }
113        }
114        support
115    }
116
117    /// The push notification services the registrar named (§8.2's `sip.pns`).
118    #[must_use]
119    pub fn services(&self) -> &[String] {
120        &self.services
121    }
122
123    /// Whether the registrar named this push service.
124    ///
125    /// The question a client has to ask after every registration, and the reason `sip.pns` exists.
126    /// `false` does not mean the registration failed — it means the binding is one nothing will
127    /// wake, and a client that never asks will sit there believing it is reachable.
128    ///
129    /// Compared case-insensitively: §8.8's registry values are tokens.
130    #[must_use]
131    pub fn supports(&self, provider: &str) -> bool {
132        self.services
133            .iter()
134            .any(|named| named.eq_ignore_ascii_case(provider))
135    }
136
137    /// Whether the registrar asked for binding refreshes even without a push (§8.2's
138    /// `sip.pnsreg`).
139    #[must_use]
140    pub fn refreshes_required(&self) -> bool {
141        self.refreshes_required
142    }
143
144    /// How long to leave between those refreshes, when the registrar said a readable number.
145    ///
146    /// `None` alongside a true [`Support::refreshes_required`] means the registrar asked for
147    /// refreshes without naming an interval this side could read; the lease's own refresh point
148    /// still applies.
149    #[must_use]
150    pub fn refresh_interval(&self) -> Option<Duration> {
151        self.refresh_interval
152    }
153
154    /// The PURR the registrar assigned this binding (§8.2's `sip.pnspurr`).
155    ///
156    /// Carried, not acted on. A PURR exists so that a request can be matched to a *stored*
157    /// binding without re-deriving it from the `pn-*` values, and the party that stores bindings
158    /// is the registrar or the proxy of §5.6 — not a user agent, which has exactly one. sipx
159    /// therefore reads it, keeps it, and hands it to the application, and does no matching with
160    /// it; that half arrives with the proxy role or not at all.
161    #[must_use]
162    pub fn purr(&self) -> Option<&str> {
163        self.purr.as_deref()
164    }
165
166    /// Whether the registrar said nothing about push at all — every registrar that does not
167    /// implement RFC 8599, which is not an error and not a refusal.
168    #[must_use]
169    pub fn is_empty(&self) -> bool {
170        self.services.is_empty() && !self.refreshes_required && self.purr.is_none()
171    }
172}
173
174/// Permission to expect the request a push notification was sent for (§4.1.3).
175///
176/// **The only way to get one is [`crate::UserAgent::woken`], and only after the binding-refresh
177/// REGISTER has succeeded.** That is the whole point of the type. §4.1.3 fixes an order — push,
178/// then REGISTER, then the request — and it is the easiest thing in this RFC to run backwards,
179/// because waiting for the INVITE is what a woken client *feels* like it should do. A client that
180/// waits without refreshing is waiting on a flow that does not exist, and the call it was woken
181/// for times out somewhere it cannot see.
182#[derive(Debug, Clone, PartialEq, Eq)]
183#[non_exhaustive]
184pub struct Pending {
185    /// The lease the binding-refresh REGISTER renewed.
186    pub lease: crate::registrar::Lease,
187    /// The PURR the registrar assigned this binding, if it assigned one (§8.2's `sip.pnspurr`).
188    pub purr: Option<String>,
189}
190
191/// Put the push parameters into a contact's URI (§4.1.2).
192///
193/// The parameters are **URI** parameters, and the difference is the whole of why this is not a
194/// `format!`. Inside the angle brackets a `;` starts a `uri-parameter`; outside them it starts a
195/// header parameter, which RFC 3261 §20 makes a different field of a different grammar. A
196/// registrar reading `Contact` URIs would never see one pasted on the outside — and would answer
197/// 200 to the registration, so nothing would say it had gone wrong.
198///
199/// A contact arriving without angle brackets gains them, because that form has nowhere to put a
200/// URI parameter at all; any header parameters after it stay outside, where they were.
201///
202/// **A contact that cannot carry the parameters is returned byte-for-byte unchanged, and warned
203/// about.** There are two such contacts and they fail for different reasons:
204///
205/// - one whose URI does not parse — including a display name whose own quoted `<` or `>` defeats
206///   the split above, since a bracket sipx picked wrongly yields text that is not a URI;
207/// - one whose URI parses under a scheme sipx does not model — `tel:`, `http:`, `urn:` — which
208///   has no `uri-parameter` list to put them in, so setting them is a no-op.
209///
210/// Returning the contact unchanged is failure-closed, and the warning is the point: the second
211/// case is otherwise indistinguishable from success. The registration would go out looking
212/// ordinary, the registrar would answer 200, and the device would be unreachable by exactly the
213/// mechanism it was configured for. This is the application's own configuration rather than
214/// network input, so it is a fault an operator can fix once told about it.
215#[must_use]
216pub fn in_contact(contact: &str, device: &Device) -> String {
217    use bytes::Bytes;
218
219    let trimmed = contact.trim();
220    let (prefix, uri_text, tail) = match (trimmed.find('<'), trimmed.rfind('>')) {
221        (Some(open), Some(close)) if open < close => (
222            trimmed.get(..=open).unwrap_or_default(),
223            trimmed.get(open + 1..close).unwrap_or_default(),
224            trimmed.get(close + 1..).unwrap_or_default(),
225        ),
226        // A bare addr-spec: the URI runs to the first semicolon, and everything from there is a
227        // header parameter list that must stay one.
228        _ => {
229            let end = trimmed.find(';').unwrap_or(trimmed.len());
230            (
231                "<",
232                trimmed.get(..end).unwrap_or_default(),
233                trimmed.get(end..).unwrap_or_default(),
234            )
235        }
236    };
237
238    let Ok(mut uri) = sipx_sip::Uri::parse(Bytes::from(uri_text.to_owned())) else {
239        tracing::warn!(
240            contact,
241            "the contact is not a URI sipx can parse, so RFC 8599 §4.1.2's push parameters were \
242             left off the registration"
243        );
244        return contact.to_owned();
245    };
246    device.set_on(&mut uri);
247    // Reading them back is the only honest check that they went in. `Uri::push_param` is a no-op
248    // on a scheme sipx does not model, because such a URI has no `uri-parameter` list at all — so
249    // without this the contact comes back looking registered for push and carrying none, which is
250    // the one failure mode this whole function exists to prevent, arrived at by another road.
251    if Device::from_uri(&uri).is_none() {
252        tracing::warn!(
253            contact,
254            "the contact's URI cannot carry a uri-parameter, so RFC 8599 §4.1.2's push \
255             parameters were left off the registration and no push notification will reach this \
256             device"
257        );
258        return contact.to_owned();
259    }
260    format!(
261        "{prefix}{}>{tail}",
262        String::from_utf8_lossy(&uri.to_bytes())
263    )
264}
265
266#[cfg(test)]
267#[allow(
268    clippy::unwrap_used,
269    clippy::expect_used,
270    clippy::panic,
271    clippy::indexing_slicing
272)]
273mod tests {
274    use super::*;
275    use bytes::Bytes;
276    use sipx_sip::{Limits, Message, parse_datagram};
277
278    /// A stub, and the only implementation of [`PushService`] anywhere near this repository.
279    struct Stub {
280        param: Option<&'static str>,
281    }
282
283    impl PushService for Stub {
284        fn provider(&self) -> &'static str {
285            "webpush"
286        }
287
288        fn prid(&self) -> &'static str {
289            "c1a5b3e7d9f2"
290        }
291
292        fn param(&self) -> Option<&str> {
293            self.param
294        }
295    }
296
297    fn device() -> Device {
298        Stub {
299            param: Some("7f3ad0"),
300        }
301        .device()
302        .expect("valid")
303    }
304
305    /// The trait exists to turn three service-specific strings into §4.1.2's parameters, and
306    /// `pn-param` is the one that is optional.
307    #[test]
308    fn a_service_becomes_the_parameters_a_contact_carries() {
309        assert_eq!(device().provider(), "webpush");
310        assert_eq!(device().param(), Some("7f3ad0"));
311        assert_eq!(device().prid(), "c1a5b3e7d9f2");
312        assert_eq!(Stub { param: None }.device().expect("valid").param(), None);
313    }
314
315    /// The failure this function exists to prevent: parameters after the `>` are header
316    /// parameters, and a registrar reading the `Contact` URI never sees them (RFC 3261 §20).
317    #[test]
318    fn the_parameters_land_inside_the_angle_brackets() {
319        assert_eq!(
320            in_contact("<sip:alice@192.0.2.5:5060>", &device()),
321            "<sip:alice@192.0.2.5:5060;pn-provider=webpush;pn-param=7f3ad0\
322             ;pn-prid=c1a5b3e7d9f2>"
323        );
324    }
325
326    /// A display name and the header parameters after the brackets belong to the header, not the
327    /// URI, and must come through untouched.
328    #[test]
329    fn a_display_name_and_header_parameters_survive() {
330        assert_eq!(
331            in_contact("\"Alice\" <sip:alice@192.0.2.5>;expires=600", &device()),
332            "\"Alice\" <sip:alice@192.0.2.5;pn-provider=webpush;pn-param=7f3ad0\
333             ;pn-prid=c1a5b3e7d9f2>;expires=600"
334        );
335    }
336
337    /// A bare addr-spec has nowhere to put a URI parameter, so it gains the brackets — and its
338    /// header parameters stay outside them, which is the half that is easy to lose.
339    #[test]
340    fn a_bare_contact_gains_the_brackets_a_uri_parameter_needs() {
341        assert_eq!(
342            in_contact("sip:alice@192.0.2.5", &device()),
343            "<sip:alice@192.0.2.5;pn-provider=webpush;pn-param=7f3ad0;pn-prid=c1a5b3e7d9f2>"
344        );
345        assert_eq!(
346            in_contact("sip:alice@192.0.2.5;expires=600", &device()),
347            "<sip:alice@192.0.2.5;pn-provider=webpush;pn-param=7f3ad0\
348             ;pn-prid=c1a5b3e7d9f2>;expires=600"
349        );
350    }
351
352    /// Configuration this side cannot parse is left alone rather than rewritten into something
353    /// else. The REGISTER will fail on its own terms, which is a fault an operator can see.
354    #[test]
355    fn a_contact_that_is_not_a_uri_is_left_as_it_was() {
356        assert_eq!(in_contact("<not a uri>", &device()), "<not a uri>");
357    }
358
359    /// A scheme sipx does not model parses perfectly well and then has nowhere to put a
360    /// `uri-parameter`. Without a check that the parameters went in, this is the one failure that
361    /// looks exactly like success: an ordinary-looking REGISTER, a 200, and a device that no push
362    /// notification can reach. Unchanged is the answer, so nothing downstream can mistake it.
363    #[test]
364    fn a_contact_whose_scheme_cannot_carry_a_uri_parameter_is_left_as_it_was() {
365        for contact in [
366            "<tel:+15551234>",
367            "tel:+15551234",
368            "<urn:service:sos>",
369            "<http://example.com/alice>",
370        ] {
371            assert_eq!(
372                in_contact(contact, &device()),
373                contact,
374                "the push parameters were silently dropped and the contact still rewritten"
375            );
376        }
377    }
378
379    /// A display name carrying its own brackets defeats the split, which yields text that is not
380    /// a URI — so it takes the failure-closed path rather than registering a contact assembled
381    /// around the wrong bracket.
382    #[test]
383    fn a_display_name_with_its_own_brackets_is_left_as_it_was() {
384        let contact = "\"Alice <at home>\" <sip:alice@192.0.2.5>";
385        assert_eq!(in_contact(contact, &device()), contact);
386    }
387
388    fn response(caps: &str) -> Response {
389        let text = format!(
390            "SIP/2.0 200 OK\r\n\
391             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
392             To: <sip:alice@example.com>;tag=r\r\n\
393             From: <sip:alice@example.com>;tag=1\r\n\
394             Call-ID: reg-1@192.0.2.5\r\n\
395             CSeq: 1 REGISTER\r\n\
396             {caps}\
397             Content-Length: 0\r\n\r\n"
398        );
399        match parse_datagram(Bytes::from(text), &Limits::datagram()).expect("parses") {
400            Message::Response(r) => r,
401            Message::Request(_) => panic!("a response"),
402        }
403    }
404
405    #[test]
406    fn the_registrars_answer_says_which_service_it_can_use() {
407        let support = Support::from_response(&response(
408            "Feature-Caps: *;+sip.pns=\"webpush\";+sip.pnsreg=\"120\"\
409             ;+sip.pnspurr=\"opaque-purr-1\"\r\n",
410        ));
411        assert!(support.supports("webpush"));
412        // §8.8's values are tokens, and a token compares case-insensitively.
413        assert!(support.supports("WebPush"));
414        assert!(support.refreshes_required());
415        assert_eq!(support.refresh_interval(), Some(Duration::from_secs(120)));
416        assert_eq!(support.purr(), Some("opaque-purr-1"));
417    }
418
419    /// The failure that looks like success: a 200, a good binding, and a push service nobody here
420    /// can reach.
421    #[test]
422    fn a_registrar_naming_another_service_does_not_support_ours() {
423        let support = Support::from_response(&response("Feature-Caps: *;+sip.pns=\"other\"\r\n"));
424        assert!(!support.supports("webpush"));
425        assert!(
426            !support.is_empty(),
427            "it said something, just not our service"
428        );
429        assert_eq!(support.purr(), None);
430    }
431
432    /// A registrar offering several services says so with several indicators, and a client that
433    /// stopped at the first would miss its own.
434    #[test]
435    fn every_named_service_is_read_not_only_the_first() {
436        let support = Support::from_response(&response(
437            "Feature-Caps: *;+sip.pns=\"other\"\r\n\
438             Feature-Caps: *;+sip.pns=\"webpush\"\r\n",
439        ));
440        assert_eq!(
441            support.services(),
442            ["other".to_owned(), "webpush".to_owned()]
443        );
444        assert!(support.supports("webpush"));
445    }
446
447    /// Every registrar that does not implement RFC 8599 answers a REGISTER perfectly well and
448    /// says nothing. That is not a refusal, and it is not an error.
449    #[test]
450    fn a_registrar_that_says_nothing_about_push_is_empty_rather_than_negative() {
451        let support = Support::from_response(&response(""));
452        assert!(support.is_empty());
453        assert!(!support.supports("webpush"));
454        assert!(!support.refreshes_required());
455    }
456}